home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-09-14 | 145.8 KB | 3,324 lines |
- Spell presents:
-
-
- ////==\\\\ || || //======== |\ /| //\\ //======\
- || || || || ||\ /|| // \\ //
- || || || || || \ / || // \\ ||
- || ||======|| ||==== || \/ || || || ||
- || || || || || || ||====|| || ==\\
- || || || || || || || || \\ ||
- || || || \\======== || || || || \\====//
-
- Issue 10
- 14-9-96
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- Index:
-
- 1. Introduction
- 1.1. About the magazine
- 1.2. About the author
- 1.3. Distribution
- 1.4. Subscribing
- 1.5. Contribuitions
- 1.6. Hellos and greets
- 2. Putting order into chaos
- 2.1. Introduction
- 2.2. BubbleSort
- 2.3. ShellSort
- 2.4. QuickSort
- 3. The fire effect
- 4. Designing a text adventure - Part III
- 4.1. Creating objects
- 4.2. Seeing the objects
- 4.3. Interacting with objects
- 5. The wonderfull world of scrolies !
- 5.1. Introduction
- 5.2. Simple scroller
- 5.3. Sine scroller
- 5.4. Mirror and water scroller
- 6. First steps into virtual reality
- 6.1. Wireframe graphics
- 6.2. Basic transformations
- 6.3. A 3D starfield
- 6.4. VectorBalls
- 7. Sprites Part III - Lots of fun stuff
- 7.1. Transparency
- 7.2. Moving over a background
- 7.3. Flipping a sprite
- 8. Graphics Part IX - Polygons
- 8.1. Simple polygons
- 8.2. Filled polygons
- 8.3. Ellipses and Arcs
- 8.4. Filled Ellipses
- 9. Hints and tips
- 10. Points of view
- 11. The adventures of Spellcaster, part 10
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 1. Introduction
-
- 1.1. About the magazine
-
- Welcome to super-special issue number 10 !! Why is this issue so special ?
- Well, because it's number 10... I never thought I would get this far, and I
- almost gave up on the 'The Mag' project (see large delay between issues 5
- and 6). But, here it is, to last... :)
- As usual, this is brought by Spellcaster, alias known as Diogo de Andrade.
- What do we have this issue ? Let me see... An article by Scorpio, about
- the fire effect. In the graphics section, I'll talk about polygons and other
- draw tools (I know I said assembler, but I'll leave that for next issue).
- In the Sprites section, I'll talk about transparency, bitmap rotation and
- moving over a background. In 'Designing a text adventure', I'll write about
- objects and object interaction. I'll also do an article on 3D basic
- transformation and some simple effects. Also, an article on different kinds
- of scrollies. On the agenda, I also have an article on sorting...
-
- You should be reading this issue in the first of the SpellUtilities,
- SpellView ! It's a program designed to view text with some features, like
- colors and other neat stuff... :)
- You probably can get it from the same place you got this issue, but if you
- can't, look in the BBS's listed somewhere in this issue, or look in my
- HomePage... It includes full source code, so you can change it, and see how
- it was done... :)
- This can only be probably seen with SpellView V1.4... Get it... It's
- better, bugfixed, etc...
-
- This magazine is dedicated to all the programmers and would-be programmers
- out there, especially to those that can't access the Net easily to get
- valuable information, to those who wish to learn how to program anything,
- from demos to games, passing through utilities and all sort of thing your
- mind can think of, and to those that can't find the right information.
-
- When you read this magazine, I'll assume some things. First, I assume you
- have Borland's Turbo Pascal, version 6 and upwards (and TASM for the assembly
- tutorials). I'll also think you have a 80386 (or 386 for short; a 486 would
- be even better), a load of patience and a sense of humor. This last is almost
- essencial, because I don't receive any money for doing this, so I must have
- fun doing it. I will also take for certain you have the 9th grade (or
- equivelent). Finally, I will assume that you have the last issues of
- 'The Mag', and that you have grasped the concepts I tried to transmit. If
- you don't have the issues, you can get them by mail, writing to one of the
- adresses shown below (Snail mail and Email).
-
- As I stated above, this magazine will be made especially for those who don't
- know where to get information, or want it all in the same place, and to those
- who want to learn how to program, so I'll try to build knowledge, building up
- your skills issue by issue. If you sometimes fail to grasp some concept, don't
- despair; try to work it out.
- That's what I did... Almost everything I know was learnt from painfull
- experience. If you re-re-re-read the article, and still can't understand it,
- just drop a line, by mail, or just plain forget it. Most of the things I
- try to teach here aren't linked to each other (unless I say so), so if you
- don't understand something, skip it and go back to it some weeks later. It
- should be clearer for you then. Likewise, if you see any terms or words you
- don't understand, follow the same measures as before.
-
- Ok, as I'm earing the Net gurus and other god-like creatures talking
- already, I'm just going to explain why I use Pascal.
- For starters, Pascal is a very good language, ideal for the beginner, like
- BASIC (yech!), but it's powerfull enough to make top-notch programms.
- Also, I'll will be using assembly language in later issues, and Pascal makes
- it so EASY to use.
- Finally, if you don't like my choice of language, you can stop whining. The
- teory behind each article is very simple, and common with any of the main
- languages (C, C++, Assembly - Yes, that's true... BASIC isn't a decent
- language).
-
- Just one last thing... The final part of the magazine is a little story
- made up by my distorted mind. It's just a little humor I like to write, and
- it hasn't got nothing to do with programming (well, it has a little), but,
- as I said before, I just like to write it.
-
- 1.2. About the author
-
- Ok, so I'm a little egocentric, but tell me... If you had the trouble of
- writing hundreds of lines, wouldn't you like someone to know you, even by
- name ?
-
- My name is Diogo de Andrade, alias Spellcaster, and I'm the creator,
- editor and writer of this magazine.
- I live in a small town called Setubal, just near Lisbon, the capital of
- Portugal... If you don't know where it is, get an encyclopedia, and look for
- Europe. Then, look for Spain. Next to it, there's Portugal, and Setubal is in
- the middle.
-
- I'm 18 years old, and I just made it in to the university (if you do want
- to know, I'm in the Technical Institute of Lisbon, Portugal), so I'm not
- a God-Like creature, with dozens of years of practice (I only program by
- eight years now, and I started in a Spectrum, progressing later to an Amiga.
- I only program in the PC for a year or so), with a mega-computer (I own a
- 386SX, 16 Mhz), that wear glasses with lens that look like the bottom of a
- bottle (I use glasses, but only sometimes), that has his head bigger than a
- pumpkin (I have a normal sized head) and with an IQ of over 220 (mine is
- actually something like 180-190). I can program in C, C++, Pascal, Assembly
- and even BASIC (yech!).
-
- So, if I am a normal person, why do I spend time writing this ?
- Well, because I have the insane urge to write thousands of words every now
- and then, and while I'm at it, I may do something productive, like teaching
- someone. I may be young, but I know a lot about computers (how humble I am;
- I know, modesty isn't one of my qualities).
-
- Just one more thing, if you ever program anything, please send to me... I
- would love to see some work you got, maybe I could learn something with it.
- Also, give me a greet in your program/game/demo... I love seeing my name.
-
- 1.3. Distribution
-
- I don't really know when can I do another issue, so, there isn't a fixed
- space of time between two issues. General rule, I will try to do one every
- month, maybe more, probably less (Eheheheh). This is getting to an issue
- every two months, so, I'll think I'll change the above text... :)
- 'The Mag' is available by the following means:
-
- - Snail Mail : My address is below, in the Contributions seccion... Just
- send me a disk and tell me what issues you want, and I
- will send you them...
-
- - E-Mail : If you E-mail me and ask me for some issues, I will Email you
- back with the relevant issues attached.
-
- - Internet : You can access the Spellcaster page and take the issues out
- of there in:
-
- http://alfa.ist.utl.pt/~l42686
-
- Follow the docs link...
-
- - Anonymous ftp : I've put this issue of 'The Mag' on the ftp.cdrom.com
- site... I don't know if they'll accept it there, because
- that's a demo only site, and my mag doesn't cover only
- demos, but anyways, try it out... It has lots of source
- code of demos.
-
- - BBS's : You can check out the BBS's list that will carry this and all
- the other issues of 'The Mag' in the file SPELL.DOC.
-
- 1.4. Subscribing
-
- If you want, I'm starting "The Mag"'s subscription list... To get
- 'The Mag' by Email every month, you just need to mail me and tell me so...
- Then, you will receive it every time a new issue is made...
-
- 1.5. Contributions
-
- I as I stated before, I'm not a God... I do make mistakes, and I don't
- have (always) the best way of doing things. So, if you think you've spotted
- an error, or you have thought of a better way of doing things, let me know.
- I'll be happy to receive anything, even if it is just mail saying 'Keep it
- up'. As all human beings, I need incentive.
-
- Also, if you do like to write, please do... Send in articles, they will be
- welcome, and you will have the chance to see your names up in lights.
- They can be about anything, for a review of a book or program that can
- help a programmer, to a point of view or a moan. I'm specially interested in
- articles explaining XMS, EMS, DMA and Soundblaster/GUS.
-
- If anyone out there has a question or wants to see an article about
- something in particular, feel free to write... All letters will be answered,
- provided you give me your address.
-
- If you have a BBS and you want it to include this magazine, feel free to
- write me... I don't have a modem, so I can only send you 'The Mag' by Email.
-
- You can also contact me personally, if you study on the IST (if you don't
- know what the IST is, you don't study there). I'm the freshman with the
- black hair and dark-brown eyes... Yes, the one that in all talkers at the
- same time... :)
-
- My adress is:
- Praceta Carlos Manito Torres, nº4/6ºC
- 2900 Setúbal
- Portugal
-
- Email: l42686@alfa.ist.utl.pt
-
- And if you want to contact me on the lighter side, get into the Lost Eden
- talker... To do that telnet to:
-
- Alfa.Ist.Utl.Pt : Port 1414
-
- If that server is down, try the Cital talker, in
-
- Zeus.Ci.Ua.PT : Port 6969
-
- I'm almost always there in the afternoon... As you may have guessed already,
- my handle is Spellcaster (I wonder why...)...
-
- 1.6. Hellos and greets
-
- I'll say hellos and thanks to all my friend, especially for those who put
- up with my not-so-constant whining.
- Special greets go to Scorpio, Denthor from Asphyxia (for the excelent VGA
- trainers), Draeden from VLA (for assembly tutorials), Dr.Shadow (Delta Team
- is still up), Joao Neves for sugestions, testing and BBS services, Garfield
- (for the 'The Mag' logo and general suport) and all the demo groups out
- there.
- I will also send greets to everybody that responded to my mag... Thank
- you very much !
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 2. Putting order into chaos
-
- 2.1. Introduction
-
- Welcome to another article by your friend Spellcaster... This article is
- about sorting an array... What's sorting, you may ask ?
- [ Everyone asks what's sorting !! ]
- Well, sorting is organizing data in a data structure... Get it ?
- [ Everybody says no... ]
- Ok, imagine you have an array like this:
-
- A[1]:=221;
- A[2]:=321;
- A[3]:=831;
- A[4]:=432;
- A[5]:=721;
-
- Sorting is converting that array into:
-
- A[1]:=221;
- A[2]:=321;
- A[3]:=432;
- A[4]:=721;
- A[5]:=831;
-
- See ? Sorting is the same as ordering...
- There are _LOTS_ of methods for doing this, but there are some that are
- 'standart'... Each one has it's advantages and disadvantages... I'll talk
- about three methods... I'll start with:
-
- 2.2. BubbleSort
-
- BubbleSort is the slowest and easiest to program method, and it is sometimes
- the best one for small and desordered arrays.
- The ideia of BubbleSort is to order an array by swapping the contents of
- two adjacent memory positions (if they are out of order). A flag assures that
- the array is ordered. Execution example:
-
- We want to sort the array [ 7 4 2 3 5 1 6 ]. Let's see:
-
- ┌──────┬───────────────┬────────────────────────────────────────────┐
- │ Step │ Array │ Comment │
- ├──────┼───────────────┼────────────────────────────────────────────┤
- │ 0 │ 7 4 2 3 5 1 6 │ The beggining │
- │ 1 │ 4 7 2 3 5 1 6 │ Because 4<7, then 4 and 7 are exchanged │
- │ 2 │ 4 2 7 3 5 1 6 │ Because 2<7, then 2 and 7 are exchanged │
- │ 3 │ 4 2 3 7 5 1 6 │ Because 3<7, then 3 and 7 are exchanged │
- │ 4 │ 4 2 3 5 7 1 6 │ Because 5<7, then 5 and 7 are exchanged │
- │ 5 │ 4 2 3 5 1 7 6 │ Because 1<7, then 1 and 7 are exchanged │
- │ 6 │ 4 2 3 5 1 6 7 │ Because 6<7, then 6 and 7 are exchanged │
- │ │ │ As the array isn't ordered yet, the process│
- │ │ │ restarts: │
- │ 7 │ 2 4 3 5 1 6 7 │ Because 2<4, then 2 and 4 are exchanged │
- │ 8 │ 2 3 4 5 1 6 7 │ Because 3<4, then 3 and 4 are exchanged │
- │ 9 │ 2 3 4 5 1 6 7 │ Because 5>4, the array remains the same │
- │ 10 │ 2 3 4 1 5 6 7 │ Because 1<5, then 1 and 5 are exchanged │
- │ .. │ ............. │ ......................................... │
- │ │ 1 2 3 4 5 6 7 │ │
- └──────┴───────────────┴────────────────────────────────────────────┘
-
- You should get the ideia by now... So, let's do a procedure that
- implements the above explained algorithm. The example program orders an array
- with 40 random inserted elements, generated from a seed (read this issue's
- hints and tips). The program also keeps tracks on how many comparissons and
- exchanges it does, so we can compare this type of sort with the others,
- because that's the way to evaluate the algorithm's overall eficiency.
- So, without further due, here's the code:
-
- Program BubbleSort;
-
- Uses Crt;
-
- Type DataType=Array[1..40] Of Integer;
-
- Var Data:DataType;
- A:Byte;
- Compare:Word;
- XChange:Word;
-
- Procedure FillData;
- Begin
- RandSeed:=122217; { Random seed }
- For A:=1 To 40 Do Data[A]:=Random(1000);
- End;
-
- Procedure WriteArray;
- Var B:Byte;
- Begin
- For A:=0 To 3 Do
- Begin
- For B:=1 To 10 Do
- Begin
- GotoXY(B*6+5,A+10);
- Write(Data[A*10+B]);
- End;
- Writeln;
- End;
- End;
-
- Procedure Sort;
- Var Flag:Boolean;
- C:Integer;
- Begin
- Compare:=0;
- XChange:=0;
- Repeat
- Flag:=True;
- For A:=1 To 39 Do
- Begin
- If Data[A]>Data[A+1] Then
- Begin
- { Exchange data }
- C:=Data[A];
- Data[A]:=Data[A+1];
- Data[A+1]:=C;
- Flag:=False;
- { Increase exchanges counter }
- Inc(XChange);
- End;
- { Increase comparisons counter }
- Inc(Compare);
- End;
- Until Flag;
-
- End;
-
- Begin
- Clrscr;
- FillData;
- GotoXY(13,8);
- Writeln('Array before BubbleSort:');
- WriteArray;
- ReadLn;
- Sort;
- Clrscr;
- GotoXY(13,8);
- WriteLn('Array after BubbleSort:');
- WriteArray;
- GotoXY(13,16);
- WriteLn('Compares=',Compare);
- GotoXY(13,17);
- Writeln('Exchanges=',XChange);
- ReadLn;
- End.
-
- Well, this is the easiest kind of sort... Let's move on to the next:
-
- 2.3. ShellSort
-
- ShellSort is the most complex (for me, at least) way of sorting...
- The problem with BubbleSort is that it exchanges values to near to each
- other... If you check the example array [ 7 4 2 3 5 1 6 ], notice that
- the number 7 must traverse almost the whole array to reach it's final
- position. Wouldn't it be great to make it go to it's position in 2 steps,
- instead of the 6 it requires ?
- Well, the ideia of ShellSort is to have an incremental factor... In reality,
- ShellSort is a special case of BubbleSort, a case where the elements that
- are compared/exchanged aren't so near to each other. The number of
- comparissons will sometimes be the same or bigger than BubbleSort, but the
- number of exchanges will be less... So, let's see an example:
- The increment selected is 4:
-
- ┌──────┬───────────────┬────────────────────────────────────────────┐
- │ Step │ Array │ Comment │
- ├──────┼───────────────┼────────────────────────────────────────────┤
- │ 0 │ 7 4 2 3 5 1 6 │ The beggining │
- │ 1 │ 5 4 2 3 7 1 6 │ Because 5<7, 5 and 7 are exchanged │
- │ 2 │ 5 1 2 3 7 4 6 │ Because 1<4, 1 and 4 are exchanged │
- │ 3 │ 5 1 2 3 7 4 6 │ Because 2<6, the array remains unaltered │
- └──────┴───────────────┴────────────────────────────────────────────┘
-
- If you look at the above example and think 'This demonstration isn't
- ended !!', you are wrong... This demonstration is more than finished.
- ShellSort requires several diferent increments, finalizing with the
- increment of 1. So, let's now adapt the array we've obtained with ShellSort
- with increment of 2:
-
- ┌──────┬───────────────┬────────────────────────────────────────────┐
- │ Step │ Array │ Comment │
- ├──────┼───────────────┼────────────────────────────────────────────┤
- │ 0 │ 5 1 2 3 7 4 6 │ The beggining │
- │ 1 │ 2 1 5 3 7 4 6 │ Because 2<5, 2 and 5 are exchanged │
- │ 2 │ 2 1 5 3 7 4 6 │ Because 1<3, the array remains unaltered │
- │ 3 │ 2 1 5 3 7 4 6 │ Because 5<7, the array remains unaltered │
- │ 4 │ 2 1 5 3 7 4 6 │ Because 3<4, the array remains unaltered │
- │ 5 │ 2 1 5 3 6 4 7 │ Because 6<7, 6 and 7 are exchenged │
- └──────┴───────────────┴────────────────────────────────────────────┘
-
- Well, the array isn't ordered already, but notice that the number 7 is
- already in the correct position... Now, you should apply ShellSort with
- increment of 1 to the array obtained from the previous ShellSorts. That is
- equal of applying BubbleSort. So, let's check out an example program...
- Again it keeps tracks of the numbers of comparissons and exchanges made:
-
- Program ShellSort;
-
- Uses Crt;
-
- Type DataType=Array[1..40] Of Integer;
-
- Var Data:DataType;
- A:Byte;
- Compare:Word;
- XChange:Word;
-
- Procedure FillData;
- Begin
- RandSeed:=122217; { Random seed }
- For A:=1 To 40 Do Data[A]:=Random(1000);
- End;
-
- Procedure WriteArray;
- Var B:Byte;
- Begin
- For A:=0 To 3 Do
- Begin
- For B:=1 To 10 Do
- Begin
- GotoXY(B*6+5,A+10);
- Write(Data[A*10+B]);
- End;
- Writeln;
- End;
- End;
-
- Procedure Sort(H:Integer);
- Var Flag:Boolean;
- B,C:Integer;
- Begin
- For A:=1+H To 40 Do
- Begin
- C:=Data[A];
- B:=A;
- While (B-H>0) And (Data[B-H]>C) Do
- Begin
- Data[B]:=Data[B-H];
- B:=B-H;
- Inc(Compare,2);
- Inc(XChange);
- End;
- Data[B]:=C;
- End;
- End;
-
- Begin
- Clrscr;
- FillData;
- GotoXY(13,8);
- Writeln('Array before ShellSort:');
- WriteArray;
- ReadLn;
- Compare:=0;
- XChange:=0;
- Sort(4);
- Clrscr;
- GotoXY(13,8);
- WriteLn('Array after ShellSort (increment 4):');
- WriteArray;
- ReadLn;
- Clrscr;
- Sort(2);
- Clrscr;
- GotoXY(13,8);
- WriteLn('Array after ShellSort (increment 2):');
- WriteArray;
- ReadLn;
- Clrscr;
- Sort(1);
- Clrscr;
- GotoXY(13,8);
- WriteLn('Array after ShellSort (increment 1):');
- WriteArray;
- GotoXY(13,16);
- WriteLn('Compares=',Compare);
- GotoXY(13,17);
- Writeln('Exchanges=',XChange);
- ReadLn;
- End.
-
- Just compare the difference between the two sorts... If you are a little
- confused by the example program, reread it, keeping in mind that:
-
- 1) The program goes from end to beggining of the array
- 2) The program finds the best position for one value. For example:
-
- Original Array: [ 7 2 3 1 5 4 ]
-
- If you use the original algorithm I talked about, then the array
- would look like this after a step (increment 2):
-
- [ 3 2 7 1 5 4 ]
-
- While the new algorithm makes the array like this:
-
- [ 3 2 5 1 7 4 ]
-
- In just one step, because it 'sticks' to number 7 until it can
- move it no further...
-
- Well, but what is the best sequence of increments ? There's just one rule
- for that... They shouldn't be multiples of each others... If it's a small
- array, that doesn't influenciate, but if it is a large array (i.e. more than
- 5000 positions), it does make a very big difference.
- This is lots of times faster than BubbleSort, but still it is slower than:
-
- 2.4. QuickSort
-
- This is harder to compreend for the begginer, because it is better done in
- a recursive form (see this issue's tricks and tips).
- So, QuickSort plays around with the ideia of partitioning the array is
- several smaller arrays, putting some order in each of the partitions.
- QuickSort doesn't really split the array in other arrays (that would need
- more memory). It just works with a part of the array at a time.
- To exemplify this one, I have to aquire another way to make the program's
- trace. So, assume that I and J as two variables that traverse the array, and
- imagine that the array we want to order is [ 44 55 12 42 94 18 6 67 ]
- So, the first thing to do in QuickSort is to select any number. Usually
- the number is in the array, altough this is not necessary. Let's choose the
- number 26. So, let's order the array in a way that all numbers smaller than
- 26 go to the beggining of the array (the left) and all the numbers that are
- bigger go to the end of the array (the right). So:
-
- Step 0: 44 55 12 42 94 18 06 67
- ^ ^
- I J
-
- So, what the algorithm does is to put I pointing to the first number it
- founds from the beggining of the array, larger than the select number (in
- this case 26). So, I points to 44. And we point J to the first number smaller
- than 26, counting from the right. In this case, J will point to 06.
- Then it switches the contents of both positions:
-
- Step 1: 06 55 12 42 94 18 44 67
- ^ ^
- I J
-
- And I and J find the next numbers than fullfill the caracteristics we've
- discussed previously. So, step after step:
-
- Step 2: 06 18 12 42 94 55 44 67
- ^ ^
- J I
-
- When J is smaller than I, that means that we have a 'final' array:
-
- [ 6 18 12 42 94 55 44 67 ]
-
- That we can think as two arrays:
-
- [ 6 18 12 ] + [ 42 94 55 44 67 ]
-
- One with elements smaller than 26 and the other with elements bigger than
- 26. So, we apply the same method to each one of the sub-arrays, until we
- reach subarrays with only one element. Then, if you 'sum' them all up, you'll
- get an ordered array. Check out the example program. In the example program,
- we'll choose the base number (the 26 in our previous example) as beeing the
- number halfway in the array (in the above example, it would be number 42 or
- 94):
-
- Program QuickSort;
-
- Uses Crt;
-
- Type DataType=Array[1..40] Of Integer;
-
- Var Data:DataType;
- A:Byte;
- Compare:Word;
- XChange:Word;
-
- Procedure FillData;
- Begin
- RandSeed:=122217; { Random seed }
- For A:=1 To 40 Do Data[A]:=Random(1000);
- End;
-
- Procedure WriteArray;
- Var B:Byte;
- Begin
- For A:=0 To 3 Do
- Begin
- For B:=1 To 10 Do
- Begin
- GotoXY(B*6+5,A+10);
- Write(Data[A*10+B]);
- End;
- Writeln;
- End;
- End;
-
- Procedure Sort;
- Var Flag:Boolean;
- I,J:Integer;
- X,N:Integer;
-
- Procedure SortSubArray(Left,Right:Byte);
- Begin
- { Partition }
- I:=Left;
- J:=Right;
- N:=Data[(Left+Right) Div 2];
- Repeat
- { Find first number from the left to be < N }
- While Data[I]<N Do
- Begin
- Inc(Compare);
- Inc(I);
- End;
- { Find first number from the right to be > N }
- While Data[J]>N Do
- Begin
- Inc(Compare);
- Dec(J);
- End;
- { Exchange }
- If I<=J Then
- Begin
- X:=Data[J];
- Data[J]:=Data[I];
- Data[I]:=X;
- Inc(I);
- Dec(J);
- Inc(XChange);
- End;
- Inc(Compare,2);
- Until J<I;
- { Order left and right subarrays }
- If Left<J Then SortSubArray(Left,J);
- If I<Right Then SortSubArray(I,Right);
- Inc(Compare,2);
- End;
-
- Begin
- SortSubArray(1,40);
- End;
-
- Begin
- Clrscr;
- FillData;
- GotoXY(13,8);
- Writeln('Array before QuickSort:');
- WriteArray;
- ReadLn;
- Compare:=0;
- XChange:=0;
- Sort;
- Clrscr;
- GotoXY(13,8);
- WriteLn('Array after QuickSort:');
- WriteArray;
- GotoXY(13,16);
- WriteLn('Compares=',Compare);
- GotoXY(13,17);
- Writeln('Exchanges=',XChange);
- ReadLn;
- End.
-
- QuickSort is only good when the value selected as the 'medium value' is
- good... Or else, it sucks ! :)
- So, let's compare the three sorts:
-
- BubbleSort ShellSort QuickSort
-
- Compares 1404 404 498
- Exchanges 430 202 78
-
- See ?! Altough ShellSort does fewer comparisons, it does almost 3 times
- more exchanges, which is a heavier operation. So, QuickSort is in fact
- a lot faster.
-
- Sorts are very important for programs, for demos and games... You'll see
- an aplication of QuickSort in one of the articles this month (the 3d article,
- the part about vectorballs). So, see you in another article... :)
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 3. The fire effect
-
- Welcome to this fine article written by Scorpio... I, Spellcaster, will
- make any comments between square brackets. Oh, by the way, I changed
- Scorpio's code a bit, in order to be compatible with the Mode13h Unit I'm
- giving you issue by issue... Sorry, Scorpio... I also cleaned up a bit his
- comments... But the code was made by him... :) Take the lead, Scorpio...
-
- Hi there. It's Scorpio again (??). This time, I am explaining you how you
- can burn down your monitor... Yep, I know... You can always dump gas on it
- and then light a match... But I want it to be a GOOD fire... one that doesn't
- actually burn your monitor, but one that actually will be so close to reality
- that'll make you feel hotter just being near the monitor... :)))))
- Enough of this... OK... There are LOTS of fires around the world that are
- FASTER, SMALLER (check #coderz 256 byte fire compo) and more beautifull than
- the one I'll show you, but mine is A LOT EASIER to understand!!! Let's hear
- (read) some stuff on the fire algorythm.
-
- o The palette
-
- To have a good-looking fire, you NEED a good palette... One that goes
- from black to orange and then to red...(256 color pal, of courz)
-
- [ You can also use a pallete that goes from black to white, then to yellow,
- then to orange and finally to red... Scorpio's original palette is in
- the file FIRE.PAL, but I've made another one, and put it in the
- FIRE2.PAL file... Try it out... Just change the name in the source to
- see it... ]
-
- o The Get/Putpixel functions
-
- Well... These functions will be used A LOT in the fire... Get yours
- instead of the one I have in the sources...(YEAH, I know that yourz
- can do 5.000.000.000 put/getpixel in 0.0000001 msec.. :-) )). Just
- use one that is fast enough.
-
- o The average-for-some-but-not-all-points-around-the-current function
-
- This is just a function that calculates the average of the colors on
- SOME (let's say 4) pixels around one pixel...
-
- OK... It seems I have a lot of explaining to do... First thing comes first
- The palette has got to have some colours that are usually on a fire, 'cos if
- you make a fire with green and blue it won't look like a fire, will it? Try
- to make a 'gradient' from black to orange and then to red that looks smooth,
- with no violent color-transitions... This way, the fire will look like a fire.
- Now, the get/putpixel functions... I have a SIMPLE MEM get/putpixel function
- 'cos EVERYBODY knows it and so there won't be any misunderstanding 'cos of a
- get/putpixel... To have a nicer effect, simply cut'n'paste YOUR get/putpixel
- functions to the source and it'll work just fine...
- Last but not least, comes the average function...This is a function that
- received 4 (see explanation below) numbers and gives the average of those
- 4 numbers.
-
- Finally... Yeah...I'm writting this at 3 a.m. and my fingers are
- starting to complain... :) How does this work??? First of all, get to
- mode 13h (??). Okay... No, I'm not joking... Now here goes a pseudo-lang
- piece of code:
-
- TOP-OF-LOOP: put-some-random-colours in bottom line of fire
- from Y:=bottom-line to upper-line do
- from X:=left-side-of-fire to right-side-of-fire do
- AVG:=average(point1,point2,point3,point4);
- putpixel(X,Y-1,AVG);
- return to TOP-OF-LOOP
-
-
- "Okay... that doesn't seem too hard to to... but WHAT are point1..point4",
- you might say... And my answer is: This is the magic of the fire algorythm...
- In the source, you'll see that I chose the point itself, the point at its
- right,the point at its left and the point above it... Why did I choose these
- 4 points? These ones seem to give me fine results... Try some other ones...
- Why do I store the average ABOVE (y-1) the current pixel?? I do it so that
- the fire goes up, i.e. the fire seems to go up but fading a little (the flames
- in the bottom are brighter).
- "HEY! I checked your source and it has different positions to put the
- average of the current pixel..." Yes, it has... Please notice that the fire
- at its upper part starts to have a bit more of movement. You can see something
- like this in the sources:
-
- MEM[VGA:320*y+x+random(3)-1]:=colour
-
- This gives a little bit of movement to the fire, and it looks G*R*E*A*T!!
- Try it yourself, and choose one that looks good to what you want to do. You
- have some more ways to do it in the source... It is V*E*R*Y well documented.
-
- [ What happened to modesty ???? :) ]
-
- It looks like it is the end of this article... As always (IM), if you have
- any problems with it, contact me at si17899@ci.uminho.pt.. If you want to
- tell me that my code sucks, email me at logname@hostname :-).
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 4. Designing a text adventure - Part III
-
- In this issue's tutorial in text adventures, we'll delve into the amazing
- and essencial world of objects !!!
- No text adventure would be complete without 100000 objects lying around
- the gamescape! For FangLore, we'll be more modest, and we'll just have four
- objects... These are:
-
- -> Shovel - Needed to knock the door down
- -> Mask - Needed to pass the room with the gas
- -> Sword - Needed to kill the monsters
- -> Treasure - Guess ?!
-
- You can include other redundanct objects, without any purpose, except to
- confuse, or maybe amuse, the player... Let's put in the game 2 redundact
- objects:
- -> Lazer gun - A prop from a previous game
- -> A strange key - No purpose at all !
-
- So, lets create the objects...
-
- 4.1. Creating objects
-
- To do so, we can use the program suplied (OBJGEN.PAS). It is very similar
- to the ROOMGEN.PAS program I gave with issue 8, except that this one creates
- rooms, while the OBJGEN.PAS program creates the objects.
- So, what do we need to store ?
- Well, it's name, it's position (we can move objects from a room to another,
- or be carrying it) and it's description... I'll explain the description part
- latter. So, let's define a record like this:
-
- Type ObjType=Record
- Name:String[80];
- Pos:Byte;
- Desc:Array[1..5] Of String[80];
- End;
-
- And let's define the number of objects in the game:
-
- Const NumberObjs=6;
-
- And, finally, let's define the array that will store the objects:
-
- Var Objects:Array[1..NumberObjs] of ObjType;
-
- So, after we've defined these data structures and made a file called
- OBJ.DAT (with the OBJGEN program) with the data, let's read the data, with
- a procedure similar in every respect with the ReadRoomData procedure that
- I gave in the first article of this series:
-
- Procedure ReadObjData; { Read from the disk the object data }
- Var F:Text;
- A,B:Byte;
- Flag:Boolean;
- Begin
- { Prepares the text file for accessing }
- Assign(F,'Obj.Dat');
- Reset(F);
- { For every object in the game }
- For A:=1 To NumberObjs Do
- Begin
- { Read the name of the objects }
- ReadLn(F,Objects[A].Name);
- { Read the initial position of the objects }
- ReadLn(F,Objects[A].Pos);
- { Clear the object's description }
- For B:=1 To 5 Do Objects[A].Desc[B]:='';
- { Read the description of the room }
- Flag:=True;
- B:=1;
- While Flag Do
- Begin
- ReadLn(F,Objects[A].Desc[B]);
- If (B=5) Or (Objects[A].Desc[B]='*') Then Flag:=False;
- Inc(B);
- End;
- End;
- Close(F);
- End;
-
- Just a note... If the POS field of the any object is equal to 0, that means
- that we are carrying the object, and if it is equal to 255, that means that
- the object is hidden (case of the gas mask, that only appears if the oven is
- open).
-
- 4.2. Seeing the objects
-
- An object is worthless, unless you can use it... And you can only use it
- if you can see it, to know it is there... So, there are two 'kinds' of
- objects you can see... Objects you own and objects that are lying around...
- A good game has different descriptions for both the 'kinds', but Fanglore
- just does the same thing in either case: spit out the description.
- So, there is a basic command that is equal in _EVERY_ text adventure in the
- world: EXAMINE !
- The examine command has a parameter following, that is, it accepts (and
- requires) that you type another thing... That thing is the thing you want
- to examine... For example, if you wanted to examine the sword, you would
- type:
- EXAMINE SWORD
-
- So, EXAMINE is the command, and SWORD is the parameter. But notice that
- you aren't limited to just examine objects... You can examine anything in
- the game... For example, the oven in the kitchen... But I'll talk about that
- later, in another article of this serie...
- Also, notice that you can only examine objects that you have or that are
- in the same room as you... So, let's check out the EXAMINE procedure:
-
- Procedure Examine(Param:String);
- Var Flag:Boolean;
- A,B:Byte;
- Begin
- Flag:=True;
- { ***************************************************** }
- { Here we'll include code for things like the oven, etc }
- { ***************************************************** }
- { Search the object in the object list }
- A:=0;
- Repeat
- Inc(A);
- If Objects[A].Name=Param Then Flag:=True;
- Until Flag Or (A>NumberObjs);
- If Flag Then
- Begin
- { The object exists }
- { Check if it is in the room or in your possession }
- If (Objects[A].Pos=0) Or (Objects[A].Pos=CurrentRoom) Then
- Begin
- { The object is 'visible'... Write description }
- Writeln;
- B:=1;
- TextColor(Yellow);
- While (B<6) And (Objects[A].Desc[B]<>'*') Do
- Begin
- Writeln(Objects[A].Desc[B]);
- Inc(B);
- End;
- Writeln;
- End
- Else
- Begin
- { The object exists, but it's not visible }
- Writeln;
- TextColor(Yellow);
- WriteLn('I can''t see the ',Param,' here...');
- WriteLn;
- End;
- End
- Else
- Begin
- { The specified parameter doesn't represent any existing }
- { object... }
- WriteLn;
- TextColor(Yellow);
- WriteLn('Sorry, but I don''t even know what is a ',Param);
- WriteLn;
- End;
- End;
-
- Of course you'll have to do the routine that calls this procedure... You
- must put it (as usual with all commands) in the Play procedure. The routine
- is like this:
-
- If Parsed[1]='EXAMINE' Then
- Begin
- Valid:=True;
- Examine(Parsed[2]);
- End;
-
- Simple, isn't it... But this is not over... If you remember what I told
- you about parsing, you know that the phrases are splitted in words. But,
- Gas Mask has two words in it's name... So, the program can't cope with
- objects with composite names. How can we make him accept it ?
- Well, we have to join the words again... Like this:
-
- If Parsed[1]='EXAMINE' Then
- Begin
- Valid:=True;
- { Join the words again }
- D:=3;
- E:=Parsed[2];
- While Parsed[D]<>'' Do
- Begin
- E:=E+' '+Parsed[D];
- Inc(D);
- End;
- Examine(E);
- End;
-
- You must define E as a string and D as a Byte types of variables.
- Altough this works with commands that only require one parammeter, this
- fails with other commands were multiple parameters are required... So,
- general rule, don't use objects with names that have spaces...
-
- But this section isn't finished yet... We still have to know what objects
- are in the room, and what objects do you carry... But I'll leave that for
- tomorrow, because I had 2 hours sleep last night... And I'm VERY tired...
- Good night... Zzzzzzzzzzzzzzzzzzzzzzzz..........
-
- Ok... I'm back ! :) Ready for more... So... The problem this moment is
- knowing what are the objects we can interact. That's easy. When you enter
- a room, you make the program print out a list of visible objects. To do
- so, just add this code to the Look procedure:
-
- TextColor(LightCyan);
- Writeln('Visible objects:');
- Flag:=False;
- For A:=1 To NumberObjs Do If Objects[A].Pos=RoomNumber Then
- Begin
- Writeln(' ',Objects[A].Name);
- Flag:=True;
- End;
- If Flag=False Then Writeln(' None');
-
- Don't forget do define the Flag variable as a Boolean. It is used to know
- if there is any objects in the room... If it is false after the For loop
- ends, that means that there aren't any objects in the room, so the program
- writes down that it doesn't see any objects...
-
- To know what objects you are carrying, you can use second best-known
- command in text adventures... The INVENTORY command !
- To do the inventory command, you just check all the objects in the game.
- If their position (the POS field) is equal to zero, then you are carrying
- it... Code:
-
- Procedure Inventory;
- Var A:Byte;
- Flag:Boolean;
- Begin
- Flag:=False;
- TextColor(LightBlue);
- WriteLn;
- WriteLn('Objects carried:');
- For A:=1 To NumberObjs Do If Objects[A].Pos=0 Then
- Begin
- WriteLn(' ',Objects[A].Name);
- Flag:=True;
- End;
- If Flag=False Then Writeln(' None');
- WriteLn;
- End;
-
- As usual, you have to add some code to the Play procedure, that calls the
- above procedure:
-
- If Parsed[1]='INVENTORY' Then
- Begin
- Valid:=True;
- Inventory;
- End;
-
- So, now you look at objects... You just have to do some interaction...
-
- 4.3. Interacting with objects
-
- An object is useless unless you can you interact somehow with it... There
- are three common interactions you can do with objects: Get, Drop and Use.
- Let's start with the easy ones: Get and Drop
- To get an object, all the program has to do is to set the POS field of
- the desired object to zero... After we verify if the object exists and if
- it is in the room, and if it isn't in your position already.
- To drop an object, you just have have to verify if you have that object
- and set the POS field of the object to the number of the room you're on...
- Coding wise:
-
- Procedure Get(O:String);
- Var A:Byte;
- Flag:Boolean;
- Begin
- Flag:=False;
- A:=0;
- Repeat
- Inc(A);
- If O=Objects[A].Name Then Flag:=True;
- Until (A>NumberObjs) Or (Flag=True);
- If Flag=True Then
- Begin
- { The object exists }
- If Objects[A].Pos=CurrentRoom Then
- Begin
- { The object is in the current room }
- { Get the object }
- Objects[A].Pos:=0;
- TextColor(LightCyan);
- WriteLn;
- WriteLn('You get the ',O);
- WriteLn;
- End
- Else
- Begin
- If Objects[A].Pos=0 Then
- Begin
- { The object is already in the possession }
- { of the player... }
- TextColor(LightCyan);
- WriteLn;
- WriteLn('You already have the ',O);
- WriteLn;
- End
- Else
- Begin
- WriteLn('You don''t see the ',O);
- WriteLn;
- End;
- End;
- End
- Else
- Begin
- { The object doesn't exist }
- WriteLn;
- TextColor(LightRed);
- Writeln('What are you talking about ?');
- Writeln;
- End;
- End;
-
- As usual, you have to put a call to this procedure in the Play procedure...
- This command has the same problem as the examine command, so we must do the
- same thing that you did in the examine command: join the parsed array in
- a string that has the name of the object:
-
- If Parsed[1]='GET' Then
- Begin
- Valid:=True;
- { Join the words again }
- D:=3;
- E:=Parsed[2];
- While Parsed[D]<>'' Do
- Begin
- E:=E+' '+Parsed[D];
- Inc(D);
- End;
- Get(E);
- End;
-
- The drop procedure is similar, altough is a bit simpler:
-
- Procedure Drop(O:String);
- Var A:Byte;
- Flag:Boolean;
- Begin
- Flag:=False;
- A:=0;
- Repeat
- Inc(A);
- If (O=Upper(Objects[A].Name)) And
- (Objects[A].Pos=0) Then Flag:=True;
- Until (A>NumberObjs) Or (Flag=True);
- If Flag=True Then
- Begin
- { The object is in the player's possession }
- Objects[A].Pos:=CurrentRoom;
- TextColor(LightCyan);
- WriteLn;
- WriteLn('You drop the ',O);
- WriteLn;
- End
- Else
- Begin
- { The object doesn't exist or it isn't in the }
- { player's possession... }
- TextColor(LightRed);
- WriteLn;
- WriteLn('You don''t have the ',O);
- WriteLn;
- End;
- End;
-
- Again, add the following code to the Play procedure... You already know
- what it does:
-
- If Parsed[1]='DROP' Then
- Begin
- Valid:=True;
- { Join the words again }
- D:=3;
- E:=Parsed[2];
- While Parsed[D]<>'' Do
- Begin
- E:=E+' '+Parsed[D];
- Inc(D);
- End;
- Drop(E);
- End;
-
- So, Get and Drop are ready... Now, let's go to the third 'class' of
- object manipulation commands: The use commands. The use commands are lots
- of commands, but they all do the same thing: Manipulate the object. For
- example... You can use the Gas Mask by typing USE MASK... But let's do things
- harder... For example, USE MASK can be thought as breaking it! You are
- using it... So, let's do the WEAR command... :))) It is harder that way for
- the player know what to do... The uses commands are just lines and lines of
- IFs, because every object has a different effect. Some types of commands
- require more than one parameter. For example, to use the sword, you must type
- USE SWORD ON MONSTER...
- In FangLore we have two use commands... The USE command and the WEAR
- command. The wear command is only used for the gas mask... The use command
- is for the others. The theory is simple... According to the object you
- select, you must do a block of instruction that define what happens.
- For example, when you use the shovel with the door in room 20, the door
- to FangLore opens... Code for the WEAR command... This is so small and
- simple that we'll include it in the Play procedure.
-
- If Parsed[1]='WEAR' Then
- Begin
- { Join the words again }
- D:=3;
- E:=Parsed[2];
- While Parsed[D]<>'' Do
- Begin
- E:=E+' '+Parsed[D];
- Inc(D);
- End;
- If E='GAS MASK' Then
- Begin
- Valid:=True;
- { Check if the player has the gas mask }
- { We know that the mask is object number 2 }
- If Objects[2].Pos=0 Then
- Begin
- Mask:=True;
- TextColor(LightCyan);
- WriteLn;
- WriteLn('You wear the gas mask...');
- WriteLn;
- End
- Else
- Begin
- TextColor(LightRed);
- WriteLn;
- WriteLn('You don''t have the gas mask.');
- WriteLn;
- End;
- End;
- End;
-
- The Mask variable is a global variable of type Boolean. It should be
- initialized to False in the Init procedure. If it is true, that means that
- we are using it. We'll see later how that can be used...
-
- Now, let's move on to the USE command... There are the following
- possibilities:
-
- USE SHOVEL ON DOOR
- USE SWORD ON MONSTER
-
- The parser should eliminate the ON prenom... Do that with the EliminPrenoms
- procedure (see details in the first article of this serie).
- So, let's do the USE procedure:
-
- Procedure Use(Arg:ParsedType);
- Var A:Byte;
- Flag:Boolean;
- Begin
- Flag:=False;
- A:=0;
- Repeat
- Inc(A);
- If (Arg[2]=Upper(Objects[A].Name)) And
- (Objects[A].Pos=0) Then Flag:=True;
- Until (A>NumberObjs) Or (Flag=True);
- If Flag=True Then
- Begin
- { The object is "usable" }
- Flag:=False;
- { Check if player want to use the shovel }
- If Arg[2]='SHOVEL' Then
- Begin
- Flag:=True;
- { Check if it is used with the door }
- If Arg[3]='DOOR' Then
- Begin
- { Check if the player is in the proper }
- { location... }
- If CurrentRoom=20 Then
- Begin
- { The player is in the right place }
- { Open passage between the garden }
- { and the mansion... }
- Rooms[20].North:=15;
- TextColor(LightCyan);
- WriteLn;
- WriteLn('You knock the door down...');
- WriteLn;
- End
- Else
- Begin
- { The player is someplace else }
- TextColor(LightRed);
- WriteLn;
- WriteLn('I don''t see a door...');
- WriteLn;
- End;
- End
- Else
- Begin
- { The player didn't used the shovel with }
- { the door... }
- TextColor(LightRed);
- WriteLn;
- WriteLn('Use it with what ?!');
- WriteLn;
- End;
- End;
- If Arg[2]='SWORD' Then
- Begin
- Flag:=True;
- { I'll include the code here in a future }
- { issue, when I'll talk about monsters... }
- End;
- End;
- If Flag=False Then
- Begin
- { No "legal" objects were used }
- TextColor(LightRed);
- WriteLn;
- WriteLn('Can't use that...');
- WriteLn;
- End;
- End;
-
- We've must passe the entire parsed array as a parameter to the procedure,
- because objects like the shovel and the sword need another 'object' to work
- with... Remember that you are not limited to USE objects... You can use,
- for instance, a lever... It is not an object because you can't carry it
- around, but you can use it... But the code must be changed slightly... In
- the case of FangLore, we can only use objects... :)
- Notice also that in the case of the USE command, we can't use the trick
- we did with the Examine, Get, Drop and Wear commands, the trick of joining
- the parsed array together to form the name of the objects with more than
- one word (i.e. GAS MASK)... So, it is good to use as the name of the objects
- single words, or else lot's of manipulations are needed... In the case of
- FangLore there isn't a problem, but in other games it might be...
- Again, you must add the following code to the Play procedure:
-
- If Parsed[1]='USE' Then
- Begin
- Valid:=True;
- Use(Parsed);
- End;
-
- So, this article is finished... Next article of this serie will cover
- monsters and special rooms... Don't miss it !! :)))
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 5. The wonderfull world of scrolies !
-
- 5.1. Introduction
-
- Welcome to another article by your good (??) friend Spellcaster...
- This one is about scrollers... Or scrollies... :)
- So, what are they ?
- Scrollers are simply a piece of text that scrolls by the screen... Almost
- every demo in the world has at least one... There are billions of types of
- scrollers. They all revolve around the same principle... Memory...
- Scrollers are simple alterations of memory... If you move the memory the
- right way, you achieve the effect...
- Graphical scrollers are very similar to text scrollers... The only
- difference is that in text scrollers the text appears a character at a time,
- and in graphical scrollers, the text appears one collumn at a time... For
- example, if the font you are using is 16 pixels width, a character appears
- after 16 calls to the procedure that scrolls... Got it ?
-
- This article makes heavy use of the Font unit that is supplied with this
- issue... All the theory behind it was explained in last issue. Read the
- source code for more details... I've also included a color font,but is
- fairly limited... I hate drawing fonts, so I used one I've made for an
- old game of mine.
- Just remember one thing about the routine... Because of a small error in
- the conception of the font, the position in the array is not denoted as
- (x,y) but (y,x)... That is, if you want to know the color of the first
- pixel of the second line of character 'A', and store that value in variable
- C, you'll have to do:
-
- C:=Font^['A',1,2]
-
- Sorry about the confusing explanation...
-
- There is one routine that will be used in some of the example programs...
- It is a procedure that draws a column of a character in the right edge of
- the screen... All the scrollers in the examples scroll from right to left,
- because it's easier to read that way. So, here's the code for that routine:
-
- Procedure PutVertLine(C:Char;Index,Y:Byte;Where:Word);
- Var B:Byte;
- Begin
- For B:=1 To 16 Do
- PutPixel(319,Y+B,Font^[C,B,Index],Where);
- End;
-
- Y is the line in which the procedure starts to draw the character...
- Also, all the scrollers start in the middle of the screen...
- So let's do it...
-
- 5.2. Simple scroller
-
- The simple scroller is so simple I shouldn't waste time describing it... :)
- The ideia is just this: You write part of a character (with the PutVertLine
- procedure given above) and you scroll the screen to the left. Code:
-
- Program Simple_Scroller;
-
- Uses Mode13h,CFont,Crt;
-
- Var Colors:RgbList;
- Txt:String;
- Ch:Byte;
- A:Byte;
-
- Procedure PutVertLine(C:Char;Index,Y:Byte;Where:Word);
- Var B:Byte;
- Begin
- For B:=1 To 16 Do
- PutPixel(319,Y+B,Font^[C,B,Index],Where);
- End;
-
- Procedure ScrollLeft;
- Var B:Byte;
- Begin
- WaitVbl;
- For B:=90 To 110 Do
- Move(Mem[VGA:(320*B+1)],Mem[VGA:(320*B)],319);
- End;
-
- Begin
- { Initialization }
- InitGraph;
- LoadFont('Font.Fnt');
- LoadPal('Font.Pal',Colors);
- SetPalette(Colors);
- Txt:='SPELLCASTER CODED THIS ';
- Txt:=Txt+'THIS SUCKS ';
- Txt:=Txt+'SO, SPELLCASTER SUCKS !!';
- Txt:=Txt+' ';
- Ch:=1;
- { Scrolling }
- Repeat
- For A:=1 To 16 Do
- Begin
- PutVertLine(Txt[Ch],A,92,VGA);
- ScrollLeft;
- End;
- Inc(Ch);
- If Ch>Length(Txt) Then Ch:=1;
- Until Keypressed;
- { Shutting down }
- CloseGraph;
- End.
-
- This is simple... The ScrollLeft procedure was taught in the graphics
- section of issue 7 of 'The Mag'... The only difference is that this procedure
- only scrolls lines 90 through 110... It does that to speed up the program.
- This is a fairly quick program, so we didn't use any virtual screens.
- This should be easy to understand. We write do the screen the column indexed
- by variable A, then we increment it (the FOR cicle), then we scroll the
- screen to the left. We do this 16 times, and then we go on to the next
- character... When we finish with the string, we start all over again...
-
- 5.3. Sine scroller
-
- This is when the thing becomes tougher... What's a sine scroller ?
- It's a scroller than goes up and down, like a sine wave... If you don't
- know what a sine wave is, go to a 8th grade book... It should come there.
- There are lot's of ways to do this, and the one I'm thinking about is a
- little bit complicated. I'll draw a bit of ASCII:
-
- |
- |AAA BBBBBB CCCCCC D....
- | AA BB BB CC CD DD ....
- | A B B C D D ....
- | AA AA BB CC DD DD ....
- | AAAAAA BBCCCC DDDDDD ....
- |
-
- ^ That is the left edge of the screen. The 'A' character represents a
- character's pixels, the 'B' character represents another character's
- pixels, etc...
- Only the top line of a character is shown, for simplicity stake...
- That is the first frame of movement... Let's see the second and third
- frame of movement:
-
- |
- |AAA BBBBBB CCCCCC E....
- | AA BB BB CC DD DD ....
- | A B B C D D ....
- | AA AB BB CC DD DD ....
- | AAAAAA BCCCCC DDDDDD ....
- |
-
- |
- |AAA BBBBBB CCCCCD E....
- | AA BB BB CC DD DE ....
- | A B B C D D ....
- | AA BB BB CC DD DD ....
- | AAAAAA CCCCCC DDDDDD ....
- |
-
- Notice that the absolute Y values are the same for every X in the screen,
- independent of the character that occupies that position...
- So, the ideia is to draw a part of the string in the screen, from X=0 to
- X=319, changing the Y according to a sine calculation, that is a function
- of X... This is harder than it looks. For example, in the first frame we
- have the 'plotting' of the string starting in character 'A' (presumably the
- first character of the string). Well, in the second frame, it also starts
- with character 'A'... So, what's the difference ?
- Well, in the second frame, the 'plotting' starts in the character's
- second column, and in the third frame, it starts on the third column, and
- so forth, until it arrives to the 16th column... There it switches to the
- first column of the next character in the string...
- The code looks awfully messy, but it works fine, and if you look carefully
- at it, it will be as clear as a day in the Carabeean... :)
- Notice that there is no need now for the ScrollLeft procedure, and that
- the PutVirtLine procedure was changed in order to enable the us to specify
- in which X coordinate to draw the column...
-
- Program Sine_Scroller;
-
- Uses Mode13h,CFont,Crt;
-
- Var Colors:RgbList;
- Txt:String;
- Ch,Ch1:Byte;
- A,Y,Index:Byte;
- X:Integer;
-
- Procedure PutVertLine(C:Char;Index,X,Y,Where:Word);
- Var B:Byte;
- Begin
- For B:=1 To 16 Do
- PutPixel(X,Y+B,Font^[C,B,Index],Where);
- End;
-
- Begin
- { Initialization }
- InitGraph;
- InitTables;
- LoadFont('Font.Fnt');
- LoadPal('Font.Pal',Colors);
- SetPalette(Colors);
- Txt:='SPELLCASTER CODED THIS ';
- Txt:=Txt+'THIS SUCKS ';
- Txt:=Txt+'SO, SPELLCASTER SUCKS !!';
- Txt:=Txt+' ';
- Ch:=1;
- { Scrolling }
- Repeat
- For A:=1 To 16 Do
- Begin
- Index:=A;
- X:=0;
- Ch1:=Ch;
- WaitVbl;
- Repeat
- Repeat
- Y:=100+Trunc(20*Sines^[X]);
- PutVertLine(Txt[Ch1],Index,X,Y,VGA);
- Inc(Index);
- Inc(X);
- Until (Index>16) Or (X>319);
- Index:=1;
- Inc(Ch1);
- If Ch1>Length(Txt) Then Ch1:=0;
- Until X>319;
- End;
- Inc(Ch);
- Until Keypressed;
- { Shutting down }
- CloseGraph;
- End.
-
- This looks bad, but after some days looking at it, it will be simple to
- understand... :) Any doubts, mail me... :)
- This could be speeded up a bit by using a precalculated table for the Y
- values... We are already using a pregenerated array for the sines. This
- could also be speeded up by using assembler... But I think you can manage
- that, if you really want... The next issue, I'll probably put more ASM code
- in the graphics section...
-
- 5.4. Mirror and water scroller
-
- Well, these two are fairly easy... Why ? Because they are effects upon
- the scrollers... So, you can have a sine-water scroller... Or a normal-water
- scroller. For the examples, I'll use the simple scroller as a basis...
-
- First, let's do the mirror scroller... That's the simpler one... The ideia
- is to flip what is scrolling around the X axis, that is, draw the image
- upside down... You could to this by drawing the scroller again, but this time
- with Y coordinates flipped... But that would be too slow to combine with a
- sine scroller... So, what I do is to grab the image that is already drawn
- and copy it below, but inverted... To do so, here's the DoMirror procedure:
-
- Procedure DoMirror;
- Var B:Byte;
- Begin
- WaitVbl;
- For B:=0 To 20 Do
- Move(Mem[VGA:(320*(90+B))],Mem[VGA:(320*(130-B))],320);
- End;
-
- It copies the 20 lines ranging from 90 to 110 (where the original scroller
- is) and puts it on the 20 lines ranging from 130 to 110 (respectivelly).
- You can addapt this procedure to mirror any effect... A nice touch is to
- dim a bit the inverted image, but I'll leave that to a future issue...
- Don't forget to call this procedure after you draw every frame. Here is a
- complete example:
-
- Program Mirror_Scroller;
-
- Uses Mode13h,CFont,Crt;
-
- Var Colors:RgbList;
- Txt:String;
- Ch:Byte;
- A:Byte;
-
- Procedure PutVertLine(C:Char;Index,Y:Byte;Where:Word);
- Var B:Byte;
- Begin
- For B:=1 To 16 Do
- PutPixel(319,Y+B,Font^[C,B,Index],Where);
- End;
-
- Procedure ScrollLeft;
- Var B:Byte;
- Begin
- WaitVbl;
- For B:=90 To 110 Do
- Move(Mem[VGA:(320*B+1)],Mem[VGA:(320*B)],319);
- End;
-
- Procedure DoMirror;
- Var B:Byte;
- Begin
- WaitVbl;
- For B:=0 To 20 Do
- Move(Mem[VGA:(320*(90+B))],Mem[VGA:(320*(130-B))],320);
- End;
-
- Begin
- { Initialization }
- InitGraph;
- LoadFont('Font.Fnt');
- LoadPal('Font.Pal',Colors);
- SetPalette(Colors);
- Txt:='SPELLCASTER CODED THIS ';
- Txt:=Txt+'THIS SUCKS ';
- Txt:=Txt+'SO, SPELLCASTER SUCKS !!';
- Txt:=Txt+' ';
- Ch:=1;
- { Scrolling }
- Repeat
- For A:=1 To 16 Do
- Begin
- PutVertLine(Txt[Ch],A,92,VGA);
- DoMirror;
- ScrollLeft;
- End;
- Inc(Ch);
- If Ch>Length(Txt) Then Ch:=1;
- Until Keypressed;
- { Shutting down }
- CloseGraph;
- End.
-
- So, here's for the mirror scroller... Now, let's move to the water
- scroller... This is a variation of the mirror scroller... The only diference
- is that the water scroller has some 'ripples' in the inverted mirror...
- How can we do that ? Simple... Instead of just copying the scroller image to
- other part of the screen, we shake it a bit, by using a random value...
- So, the DoMirror procedure is replaced by teh DoWater procedure:
-
- Procedure DoWater;
- Var B:Byte;
- Begin
- WaitVbl;
- For B:=0 To 20 Do
- Move(Mem[VGA:(320*(90+B))],
- Mem[VGA:(320*(130-B)+Random(3))],316);
- End;
-
- The rest remains the same... Look at the complete program:
-
- Program Water_Scroller;
-
- Uses Mode13h,CFont,Crt;
-
- Var Colors:RgbList;
- Txt:String;
- Ch:Byte;
- A:Byte;
-
- Procedure PutVertLine(C:Char;Index,Y:Byte;Where:Word);
- Var B:Byte;
- Begin
- For B:=1 To 16 Do
- PutPixel(319,Y+B,Font^[C,B,Index],Where);
- End;
-
- Procedure ScrollLeft;
- Var B:Byte;
- Begin
- WaitVbl;
- For B:=90 To 110 Do
- Move(Mem[VGA:(320*B+1)],Mem[VGA:(320*B)],319);
- End;
-
- Procedure DoWater;
- Var B:Byte;
- Begin
- WaitVbl;
- For B:=0 To 20 Do
- Move(Mem[VGA:(320*(90+B))],
- Mem[VGA:(320*(130-B)+Random(3))],316);
- End;
-
- Begin
- { Initialization }
- InitGraph;
- LoadFont('Font.Fnt');
- LoadPal('Font.Pal',Colors);
- SetPalette(Colors);
- Txt:='SPELLCASTER CODED THIS ';
- Txt:=Txt+'THIS SUCKS ';
- Txt:=Txt+'SO, SPELLCASTER SUCKS !!';
- Txt:=Txt+' ';
- Ch:=1;
- { Scrolling }
- Repeat
- For A:=1 To 16 Do
- Begin
- PutVertLine(Txt[Ch],A,92,VGA);
- DoWater;
- ScrollLeft;
- End;
- Inc(Ch);
- If Ch>Length(Txt) Then Ch:=1;
- Until Keypressed;
- { Shutting down }
- CloseGraph;
- End.
-
- Instead of using random values, you can use some sort of pregenerated
- table or sine wave to give a much smoother look...
- Ok, this article is finally finished... :)
- Scrollers are a pain in the ass in a lot of demos, but if you add some
- cool effects, you can make them cool things... So try to do a 3d-texture-
- -and-bump-mapped-phong-shaded-faster-than-light scroller... :)))
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 6. First steps into virtual reality
-
- Welcome to the first article about 3d on 'The Mag'... Nowadays, everyone
- is excited with virtual reality and other stuff like that. Everytime I
- read the rec.games.programmer, I read tons of articles that say 'I wanna make
- a DOOM like game'... And in comp.sys.ibm.pc.demos, I read articles that say
- 'I wanna do a texture-mapped phong-shaded duck rotating in a voxel
- landscape !'... So, 3d is everywhere ! But let's move on...
-
- Let's think about the principle behind 3d. The ideia is to have a model of
- some kind and convert to an image. A model is a set of data that describe the
- virtual 'world'.
- As everyone knows, 3d stands for three-dimensions... But the computer screen
- only has 2 dimensions, so, every object in the computer can and is identified
- with 2 coordinates, X and Y, while everything in the real is described with
- 3 coordinates, X, Y and Z... It's fairly easy to estabilish the relation
- Z/Depth... But that's not always true... You can make Y your depth, or even
- an arbitrary axis... But it is convencioned that X is horizontal, Y is
- vertical and Z is the depth... So let's play with that...
- So, the main problem with 3d is to transform (x,y,z) points, in (x,y)
- points... So, how can we do that ?
- It's easier than you think... As you know the further away is an object,
- the closer to the horizon's center it is... So, the greater the Z coordinate,
- the closer it will be to the center of the screen. If we make all the
- coordinates relative to the center of the screen, the bigger the Z, the
- smaller will be X and Y... What's the operation that does this ? Division...
- So, given the (X,Y,Z) that describe a point in 3d space, the transformed
- coordinates (Xt,Yt) will be:
-
- Xt= X*256 Yt= Y*256
- ----- -----
- Z Z
-
- Why is that multiplication by 256 there ? Well, that's the camera factor.
- If we don't multiply, all would look pretty ugly, because the number would
- be too small... Why did I choose 256 ? For two reasons... First, that number
- looks good (if you use other numbers, you can even get a fish-eyed lens
- effect). Second, multiplication by 256 can be achieve by a lightning-fast
- shift-left operation, if we shift 8 bits... So, the procedure that converts
- 3 dimensions in 2 dimensions is the following:
-
- Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
- Begin
- Xt:=160+Trunc((X*256/Z));
- Yt:=100+Trunc((Y*256/Z));
- End;
-
- We use reals for coordinates because of precision of rotations...
- There are problem with this routine if the Z is 0... But you don't need
- to compute points that have the Z equal to 0, because they are on top of
- the camera. Don't forget that the X,Y and Z are relative to the center of
- the virtual 'world', that is, relative to the camera. That 160 and 100 on
- the procedure are the center of the screen.
- When you plot a point in the screen, don't forget to check if the point
- lies inside the screen! I forgot that when I was coding an example program
- and it was giving me an headache... Here is a routine that plots a 3d point
- on the screen:
-
- Procedure Plot3d(X,Y,Z:Real;Color:Byte;Where:Word);
- Var Xt,Yt:Integer;
- Begin
- Xt:=160+Trunc(X*256/Z);
- If (Xt<0) Or (Xt>319) Then Exit;
- Xt:=100+Trunc(Y*256/Z);
- If (Yt<0) Or (Yt>199) Then Exit;
- Mem[Where:(320*Yt+Xt)]:=Color;
- End;
-
- Exit is a nifty command that makes Pascal exit the block it is in... If
- you do Exit inside a function or a procedure, the program goes back to where
- that function or procedure was called... If you do Exit in the main program,
- the program will end imediately. Why do we calculate the Xt and check if
- it is in the screen ? Because it is faster, if the point is outside the
- screen... If the point is outside the screen, there's no use for the Yt
- coordinate...
- Now that we've already drawn a 3d point, we can move on to the next
- section...
-
- 6.1. Wireframe graphics
-
- What are wireframe graphics ?
- Well, wireframe graphics are a kind of 3d graphics only made of points
- and lines connecting points, without any kind of shading, texturing or
- hidden-surface removal...
- So, to do wireframe graphics, we need a kind of structure that allows us
- to store the point's data and the information of what points are connect to
- each others... In our example, we'll use a static structure, but you could
- use a dinamic one... First, let's define the maximum number of points and
- lines an object can have:
-
- Const MaxPoints=30;
- MaxLines=30;
-
- Now, let's define the structures necessary:
-
- Type Point3d=Record
- X,Y,Z:Real;
- End;
-
- Object3d=Record
- NumberPoints:Byte;
- NumberLines:Byte;
- Pt:Array[1..MaxPoints] Of Point3d;
- Lines:Array[1..MaxLines,1..2] Of Byte;
- End;
-
- So, now we need to create an object:
-
- Var Car:Object3d;
-
- And afterwards, we need to load some data into the object... To do so, I
- made a small utility that enables you to type in 3d coordinates and to type
- which points connect, and store that data in a file. Then, you can read it
- back to Object3d variable with the LoadData procedure... The code for that
- procedure is below... The utility includes the source, so that you can change
- it anyway you see fit. The program is called 3DGEN.PAS. Notice that the
- program only uses integer numbers, integer's that are loaded into reals.
-
- Procedure Load3d(Filename:String;Var Obj:Object3d);
- Var F:Text;
- A:Byte;
- Begin
- Assign(F,Filename);
- Reset(F);
- ReadLn(F,Obj.NumberPoints);
- ReadLn(F,Obj.NumberLines);
- For A:=1 To Obj.NumberPoints Do
- ReadLn(F,Obj.Pt[A].X,Obj.Pt[A].Y,Obj.Pt[A].Z);
- For A:=1 To Obj.NumberLines Do
- ReadLn(F,Obj.Lines[A,1],Obj.Lines[A,2]);
- Close(F);
- End;
-
- Now, all we have to do is to join the points that are to be joined.
- Let's check out how this is done:
-
- Procedure Draw3d(Obj:Object3d;XOff,YOff,ZOff:Integer;
- Color:Byte;Where:Word);
- Var A:Byte;
- Pt1,Pt2:Byte;
- X1,Y1,X2,Y2:Integer;
- Begin
- For A:=1 To Obj.NumberLines Do
- Begin
- Pt1:=Obj.Lines[A,1];
- Pt2:=Obj.Lines[A,2];
- Conv3d(Obj.Pt[Pt1].X+XOff,
- Obj.Pt[Pt1].Y+YOff,
- Obj.Pt[Pt1].Z+ZOff,
- X1,Y1);
- Conv3d(Obj.Pt[Pt2].X+XOff,
- Obj.Pt[Pt2].Y+YOff,
- Obj.Pt[Pt2].Z+ZOff,
- X2,Y2);
- LineC(X1,Y1,X2,Y2,Color,Where);
- End;
- End;
-
- So, what's the deal here ? Well, simple... You have two lists in a
- structure. One has the coordinates of all the points in the object, and the
- other has the list the lines there are, stored by number of the point. For
- example, if
- Ln[1,1]:=1;
- Ln[1,2]:=5;
-
- That would mean that point 1 would connect to point 5... So, we traverse
- all the Lines array and connect the points to form the image... The XOff,
- YOff and ZOff variables are the offsets of the object... The object can be
- drawn in any region of 3d space. The LineC procedure that is called is a
- clipped version of the Line procedure I gave you in issue 4 of 'The Mag'.
- Check it's source in the Mode13h unit... The only diference between LineC
- and Line is that LineC checks if the point is within the borders of the
- screen.
- Let's see the complete example:
-
- Program WireFrame;
-
- Uses Mode13h,Crt;
-
- Const MaxPoints=30;
- MaxLines=30;
-
- Type Point3d=Record
- X,Y,Z:Real;
- End;
-
- Object3d=Record
- NumberPoints:Byte;
- NumberLines:Byte;
- Pt:Array[1..MaxPoints] Of Point3d;
- Lines:Array[1..MaxLines,1..2] Of Byte;
- End;
-
- Var A:Integer;
- Car:Object3d;
- D:Char;
-
- Procedure Conv3d(X,Y,Z:Real;Var Xt,Yt:Integer);
- Begin
- Xt:=160+Trunc((X*256/Z));
- Yt:=100+Trunc((Y*256/Z));
- End;
-
- Procedure Load3d(Filename:String;Var Obj:Object3d);
- Var F:Text;
- A:Byte;
- Begin
- Assign(F,Filename);
- Reset(F);
- ReadLn(F,Obj.NumberPoints);
- ReadLn(F,Obj.NumberLines);
- For A:=1 To Obj.NumberPoints Do
- ReadLn(F,Obj.Pt[A].X,Obj.Pt[A].Y,Obj.Pt[A].Z);
- For A:=1 To Obj.NumberLines Do
- ReadLn(F,Obj.Lines[A,1],Obj.Lines[A,2]);
- Close(F);
- End;
-
- Procedure Draw3d(Obj:Object3d;XOff,YOff,ZOff:Integer;
- Color:Byte;Where:Word);
- Var A:Byte;
- Pt1,Pt2:Byte;
- X1,Y1,X2,Y2:Integer;
- Begin
- For A:=1 To Obj.NumberLines Do
- Begin
- Pt1:=Obj.Lines[A,1];
- Pt2:=Obj.Lines[A,2];
- Conv3d(Obj.Pt[Pt1].X+XOff,
- Obj.Pt[Pt1].Y+YOff,
- Obj.Pt[Pt1].Z+ZOff,
- X1,Y1);
- Conv3d(Obj.Pt[Pt2].X+XOff,
- Obj.Pt[Pt2].Y+YOff,
- Obj.Pt[Pt2].Z+ZOff,
- X2,Y2);
- LineC(X1,Y1,X2,Y2,Color,Where);
- End;
- End;
-
- Begin
- Initgraph;
- Load3d('Car.3d',Car);
- SetColor(1,63,63,0);
- For A:=-300 To 300 Do
- Begin
- Draw3d(Car,-20,0,A,1,VGA);
- Draw3d(Car,-20,0,A,0,VGA);
- End;
- D:=Readkey;
- Closegraph;
- End.
-
- 6.2. Basic transformations
-
- Everything in 3d should be done with matrixes, since they ease up a lot
- of calculations... I will explain latter why is this true.
- Matrixes are a very important part of algebra. So, I'll start this section
- by explaining basic matrix calculus. Imagine an 3x3 array and a 1x3 array:
-
- ┌ ┐ ┌ ┐
- │1 2 3│ │1│
- A= │4 5 6│ B= │2│
- │7 8 9│ │3│
- └ ┘ └ ┘
-
- Matrixes are identified by a uppercase letter. B is also called a vector,
- and because it has 3 numbers, it is called a three-dimensional vector. Notice
- that you can think B as three coordinates to a point in 3d: X=1, Y=2 and Z=3.
- With this in mind, let's see matrix multiplication. Let's multiply A by
- B:
-
- ┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐
- │1 2 3│ │1│ │1*1+2*2+3*3│ │14│
- A*B= │4 5 6│*│2│=│4*1+5*2+6*3│=│32│
- │7 8 9│ │3│ │7*1+8*2+9*3│ │50│
- └ ┘ └ ┘ └ ┘ └ ┘
-
- This is an example, only... The rule is:
-
- ┌ ┐ ┌ ┐ ┌ ┐
- │a b c│ │x│ │ax+by+cz│
- A*B= │d e f│*│y│=│dx+ey+fz│
- │g h i│ │z│ │gx+hy+iz│
- └ ┘ └ ┘ └ ┘
-
- You can multiply matrixes of any size, but we only need to know how to
- multiply a 3x3 matrix by a 1x3 matrix and a 4x4 matrix by a 1x4 matrix.
- There's just one rule to matrix multiplication: The number of columns of
- the first matrix must be equal to the number of lines of the second matrix.
- Here's the rule for 4x4 by 1x4 multiplication:
-
- ┌ ┐ ┌ ┐ ┌ ┐
- │a b c d│ │v│ │ax+by+cz+dv│
- A*B= │e f g h│*│x│=│ex+fy+gz+hv│
- │i j k l│ │y│ │ix+jy+kz+lv│
- │m n o p│ │z│ │mx+ny+oz+pv│
- └ ┘ └ ┘ └ ┘
-
- Other thing you should know is the identity matrix:
-
- 3x3 identity matrix: ┌ ┐ 4x4 identity matrix: ┌ ┐
- │1 0 0│ │1 0 0 0│
- │0 1 0│ │0 1 0 0│
- │0 0 1│ │0 0 1 0│
- └ ┘ │0 0 0 1│
- └ ┘
-
- Plainly put, the identity matrix is a matrix with all the elements zero,
- except the elements in the main diagonal. The identity matrix has the property
- of not afecting the multiplication. So:
-
- ┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐
- │1 0 0│ │5│ │1*5+0*7+0*8│ │5│
- │0 1 0│*│7│=│0*5+1*7+0*8│=│7│
- │0 0 1│ │8│ │0*5+0*7+1*8│ │8│
- └ ┘ └ ┘ └ ┘ └ ┘
-
- Other thing that you need to know is multiplying two 3x3 matrixes and
- two 4x4 matrix:
-
- ┌ ┐ ┌ ┐ ┌ ┐
- │a b c│ │j k l│ │aj+bm+cp ak+bn+cq al+bo+cr│
- │d e f│*│m n o│=│dj+em+fp dk+en+fq dl+eo+fr│
- │g h i│ │p q r│ │gj+hm+ip gk+hn+iq gl+ho+ir│
- └ ┘ └ ┘ └ ┘
-
- Multiplying two 4x4 matrixes is similar. The rules for multiplying
- matrixes of any size are the following:
-
- - The number of columns of the first matrix must be equal to
- the number of lines of the second matrix.
- Example: You want to multiply two matrixes, with dimensions
- AxB and CxD (where A and C are the number of columns).
- You can only multiply if A and D are equal.
- - The result matrix has the same number of columns of the second
- matrix and the same number of lines as the number of columns of
- the first matrix.
- Example: The result matrix of multiplying the above matrixes
- would have C columns and A lines.
- - The item (item is one of the factors of the matrix) in position
- (x,y) of the matrix will be equal to the multiplication of line
- number Y with columns number X.
- - Matrix multiplication is NOT comutative, that is, you can not
- change the order of the operands !
-
- This is the basic stuff on matrixes... If you want to know more, go to
- any good algebra book... If I forgot something, I'll tell you in the
- explanation.
- Keep this in mind, because we'll use this later... Now, let's move on to
- the theory of the diferent transformations... Let's start on translation...
- Translation is the easiest operation of them all. Translating an object means
- moving it from it's current position to other position. To translate an
- object, you just have to add to it's coordinates a number. So, let's do a
- translation procedure:
-
- Procedure Translate(Var Obj:Object3d;XOff,YOff,ZOff:Integer);
- Var A:Byte;
- Begin
- For A:=1 To Obj.NumberPoints Do
- Begin
- Obj.Pt[A].X:=Obj.Pt[A].X+XOff;
- Obj.Pt[A].Y:=Obj.Pt[A].Y+YOff;
- Obj.Pt[A].Z:=Obj.Pt[A].Z+ZOff;
- End;
- End;
-
- Easy, isn't it ?
- Now, let's do something you'll think it's pretty useless, but that can have
- it's uses... Let's put the translation in a matrix form:
-
- The formula used for translation is as follows:
-
- X = Xo+XOff
- Y = Yo+YOff
- Z = Zo+ZOff
-
- Where X, Y and Z are the translated coordinates; Xo, Yo and Zo are the
- original coordinates and XOff, YOff and ZOff are the offsets of the
- coordinates. Putting this in matrix form:
-
- ┌ ┐ ┌ ┐┌ ┐ ┌ ┐
- │ X │ │ 1 0 0 0 ││Xo│ │Xo+XOff│
- P = │ Y │ = │ 0 1 0 0 ││Yo│ = │Yo+YOff│
- │ Z │ │ 0 0 1 0 ││Zo│ │Zo+ZOff│
- │ 1 │ │XOff YOff ZOff 1 ││ 1│ │ 1 │
- └ ┘ └ ┘└ ┘ └ ┘
-
- We'll see later the use of this... Don't forget you can discard that last
- one in the result column... :)
- So, let's move on to other basic transformation: Scaling ! Let's see a
- 2d example... The 3d theory is equal.
- Scaling is the operation that transforms this:
-
- │ │
- │ │
- │ │ +----+
- │ +--+ in this: │ | |
- │ | | │ | |
- │ +--+ │ +----+
- └─────────────── └───────────────
-
- This is a scaling by two... To do so, we just have to multiply every point
- by two, right ?
- WRONG! If you multiply each point by two, all you'll get is a translation:
-
- │ │
- │ │
- │ │ +--+
- │ +--+ *2 = │ | |
- │ | | │ +--+
- │ +--+ │
- └─────────────── └───────────────
-
- So, what do we need to do ?
- Check out this case:
-
- │ │
- │ │
- │ +---+
- +-+ | │ |
- ────────|┼|──────── *2= ───────|─┼─|────────
- +-+ | │ |
- │ +---+
- │ │
- │ │
-
- Have you figured out what you have to do already ?
- You have to move the object to the center of the world (point [0,0,0]), to
- be able to scale it properly... Procedure to scale:
-
- Procedure Scale(Var Obj:Object3d;XScl,YScl,ZScl:Real);
- Var A:Byte;
- Begin
- For A:=1 To Obj.NumberPoints Do
- Begin
- Obj.Pt[A].X:=Trunc(Obj.Pt[A].X*XScl);
- Obj.Pt[A].Y:=Trunc(Obj.Pt[A].Y*YScl);
- Obj.Pt[A].Z:=Trunc(Obj.Pt[A].Z*ZScl);
- End;
- End;
-
- This procedure can have other uses... You can shrink an image, by
- specifying a scaling factor smaller than 1. If you specify a negative number,
- the object will be inverted. If you don't want to scale a certain axis,
- specify 1, not 0, because 0 would nulify that coordinates... Now, let's see
- scaling in a matrix form:
-
- X = Xo*XScl
- Y = Yo*YScl
- Z = Zo*ZScl
-
- Where X, Y and Z are the scaled coordinates; Xo, Yo and Zo are the original
- coordinates and XScl, YScl and ZScl are the scaling factors... Putting this
- in matrix form:
-
- ┌ ┐ ┌ ┐┌ ┐ ┌ ┐
- │ X │ │ XScl 0 0 ││Xo│ │Xo*XScl│
- P = │ Y │ = │ 0 YScl 0 ││Yo│ = │Yo*YScl│
- │ Z │ │ 0 0 ZScl ││Zo│ │Zo*ZScl│
- └ ┘ └ ┘└ ┘ └ ┘
-
- See ? Easy... :)
- Now we can see the use for matrixes. Imagine that some transformation
- involved translation AND scaling. You can do both in just one step... All
- you have to do is multiply the matrixes that do these transformations. So,
- matrix for this operation is:
-
- ┌ ┐ ┌ ┐ ┌ ┐
- │ 1 0 0 0 │ │ XScl 0 0 0 │ │ XScl 0 0 0 │
- │ 0 1 0 0 │ │ 0 YScl 0 0 │ │ 0 YScl 0 0 │
- │ 0 0 1 0 │*│ 0 0 ZScl 0 │=│ 0 0 ZScl 0 │
- │XOff YOff ZOff 1 │ │ 0 0 0 1 │ │ XOff*XScl YOff*YScl ZOff*ZScl 1 │
- └ ┘ └ ┘ └ ┘
-
- Applying a vector:
-
- ┌ ┐┌ ┐ ┌ ┐
- │ XScl 0 0 0 ││Xo│ │ Xo*XScl │
- │ 0 YScl 0 0 ││Yo│ │ Yo*YScl │
- │ 0 0 ZScl 0 ││Zo│=│ Zo*ZScl │
- │ XOff*XScl YOff*YScl ZOff*ZScl 1 ││1 │ │Zo*XOff*ZScl+Yo*YOff*ZScl+Zo*ZOff*ZScl│
- └ ┘└ ┘ └ ┘
-
- In equation formula:
-
- X=(Xo+XOff)*XScl;
- Y=(Yo+YOff)*YScl;
- Z=(Zo+ZOff)*ZScl;
-
- This is what you expected to found... In this case, matrix multiplication
- isn't very useful, but in cases with lots of transformations, including
- rotations and other things like that, this becomes very useful !
- Now, let's move on to the other type of transformation, and probably one
- of the most importants... It can be even more important than translation.
- But it is also harder... Yep, I'm talking about rotations...
- We'll first analise the case of planar rotation, that is, a rotation that
- is in order to a plane, or (if you prefer) an axis... I'll explain later how
- to generalize this to 3d rotation...
-
- So, in a rotation, you want to transform point (x,y) in a point (x',y').
- Below is an example of a 45 degrees rotation:
-
- Y Y
- │ │
- │ O (x',y')
- │ O (x,y) |
- │ / --> |
- │ / |
- │/alpha |beta
- └───────────────X └───────────────X
-
- Alpha is the angle the point makes with the X axis... In this case, it is
- 45 degrees. Beta is the angle the transformed point makes with the X axis,
- in this case, 90 degrees. Notice that if this was a 3d rotation, it would be
- a rotation around the Z axis... That means that the Z coordinate of the points
- is never changed. Let's do some formulas to get to the final one...
- We know (or if you don't know, get an 8th grade book and read the trigono-
- metry part) that a point can be expressed by an (X,Y) ordinated pair or
- a (P,A) pair, in which p is the distance of the point to the axis and a is
- the angle the point makes with one of the other axis. We also know the
- relations between the two systems of coordinates:
-
- X = P*Cos(A)
- Y = P*Sin(A)
-
- P = Sqrt(X^2+Y^2)
- A = ArcCos(X/P) = ArcSin(Y/P)
-
- Sqrt is the square root...
- The (X,Y) coordinate system is called the carthesian system, and the (P,A)
- system is called the polar coordinates system.
- Let's imagine we have point A, with carthesian coordinates (X,Y). That would
- convert in polar coordinates (P1,A1), like this:
-
- P1 = Sqrt(X^2+Y^2)
- A1 = ArcCos(X/P1) = ArcSin(Y/P1)
-
- and we want to rotate the point by an angle B. That would get us point C, with
- polar coordinates (P2,A2). As we can see from the above example (and from simple
- maths), P1 is equal to P2. Now, we want to obtain the carthesian coordinates
- of point C. So, from the above formulas we know that the carthesian coordinates
- of point C are:
- X' = P1*Cos(A2)
- Y' = P1*Sin(A2)
-
- But, we also know that A2=A1+B, because B is the angle we rotated and A1 is
- the original angle. So:
- X' = P1*Cos(A1+B)
- Y' = P1*Sin(A1+B)
-
- Again, with a simple knowledge of trigonometry, we know that:
-
- Sin(U+V) = Sin(U)*Cos(V) + Cos(U)*Sin(V)
- Cos(U+V) = Cos(U)*Cos(V) - Sin(U)*Sin(V)
-
- So, applying to the formula above:
-
- X' = P1*[Cos(A1)*Cos(B)-Sin(A1)*Sin(B)]
- Y' = P1*[Sin(A1)*Cos(B)+Cos(A1)*Sin(B)]
-
- From above, we know that:
-
- A1 = ArcCos(X/P1) = ArcSin(Y/P1)
-
- And from that, we derive that:
-
- Cos(A1) = X/P1
- Sin(A1) = Y/P1
-
- So, applying in the other formula:
-
- X' = P1*[(X/P1)*Cos(B)-(Y/P1)*Sin(B)]
- Y' = P1*[(Y/P1)*Cos(B)+(X/P1)*Sin(B)]
-
- And again, applying simple maths, we get the final formula:
-
- X' = X*Cos(B) - Y*Sin(B)
- Y' = Y*Cos(B) + X*Sin(B)
-
- See ? It is fairly easy to calculate... Now, let's do a simple procedure:
-
- Procedure RotateZ(Var Obj:Object3d;Deg:Integer);
- Var A:Byte;
- Angle:Real;
- XTemp:Real;
- Begin
- Angle:=0.0175*Deg;
- For A:=1 To Obj.NumberPoints Do
- With Obj.Pt[A] Do
- Begin
- XTemp:=X;
- X:=XTemp*Cos(Angle)-Y*Sin(Angle);
- Y:=Y*Cos(Angle)+XTemp*Sin(Angle);
- End;
- End;
-
- Angle is the equivalent to Deg, but expressed in radians. We need that
- because the Sin and Cos functions use radians. We must use a temporary
- variable, because the X value is altered in the first expression, but we
- need X's old value for the second expression...
- As usual, let's move this to matrix form... We need a 2x2 matrix:
-
- X' = X*Cos(Ang) + Y*Sin(Ang)
- Y' = Y*Cos(Ang) - X*Sin(Ang)
-
- The matrix corresponding to these formulas is:
-
- ┌ ┐
- │ Cos(Ang) -Sin(Ang) │
- │ Sin(Ang) Cos(Ang) │
- └ ┘
-
- Adapting this to a 3d rotation, we'll get:
-
- ┌ ┐
- │ Cos(Ang) -Sin(Ang) 0 │
- │ Sin(Ang) Cos(Ang) 0 │
- │ 0 0 1 │
- └ ┘
-
- Remember what I said about the Z coordinate being unaltered by the
- rotation ? That's why the Z line isn't altered (the Z column is the line),
- and Z doesn't enter any of the calculations (the Z collumn isn't altered).
- Now, if you change the name of the axis, you can get the formulas for the
- rotation around the other axis... The formulas, matrixes and procedures that
- achieve these effects follow...
-
- Around the X axis:
-
- Z' = Z*Cos(Ang) + Y*Sin(Ang)
- Y' = Y*Cos(Ang) - Z*Sin(Ang)
-
- ┌ ┐
- │ 1 0 0 │
- │ 0 Cos(Ang) -Sin(Ang) │
- │ 0 Sin(Ang) Cos(Ang) │
- └ ┘
-
- Procedure RotateX(Var Obj:Object3d;Deg:Integer);
- Var A:Byte;
- Angle:Real;
- ZTemp:Real;
- Begin
- Angle:=0.0175*Deg;
- For A:=1 To Obj.NumberPoints Do
- With Obj.Pt[A] Do
- Begin
- ZTemp:=Z;
- Z:=ZTemp*Cos(Angle)-Y*Sin(Angle);
- Y:=Y*Cos(Angle)+ZTemp*Sin(Angle);
- End;
- End;
-
-
- Around the Y axis:
-
- X' = X*Cos(Ang) + Z*Sin(Ang)
- Z' = Z*Cos(Ang) - X*Sin(Ang)
-
- ┌ ┐
- │ Cos(Ang) 0 -Sin(Ang) │
- │ 0 1 0 │
- │ Sin(Ang) 0 Cos(Ang) │
- └ ┘
-
- Procedure RotateY(Var Obj:Object3d;Deg:Integer);
- Var A:Byte;
- Angle:Real;
- XTemp:Real;
- Begin
- Angle:=0.0175*Deg;
- For A:=1 To Obj.NumberPoints Do
- With Obj.Pt[A] Do
- Begin
- XTemp:=X;
- X:=XTemp*Cos(Angle)-Z*Sin(Angle);
- Z:=Z*Cos(Angle)+XTemp*Sin(Angle);
- End;
- End;
-
- You can see in the file 3D1.PAS an example program of all the rotations
- being made, one at a time.
- Notice that we have to move the objects to the origin of the world, in
- order for the rotation work correctly... That's because the origin (0,0,0)
- is the center of rotation... If you want another point to be the origin,
- you have to calculate the coordinates of the points of the object relative to
- that point, rotate those relative coordinates, and then calculate those
- coordinates relatively to the origin of the world. That's easier to do than
- it sounds... Just some subtractions and addictions...
- Now, imagine you wanted to rotate the object in the three axis... You just
- rotated around the various axis, using the formulas... To do so, you could
- use the following procedure:
-
- Procedure Rotate(Var Obj:Object3d;XRot,YRot,ZRot:Integer);
- Begin
- RotateX(Obj,XRot);
- RotateY(Obj,XRot);
- RotateZ(Obj,XRot);
- End;
-
- In 3D2.PAS you can see an example of this...
- Another way you can do the three rotations is by joining the three matrixes
- of rotation together... Like this:
-
- ┌ ┐ ┌ ┐ ┌ ┐
- │ Cos(a) -Sin(a) 0 │ │ Cos(a) 0 -Sin(a) │ │ 1 0 0 │
- │ Sin(a) Cos(a) 0 │*│ 0 1 0 │*│ 0 Cos(a) -Sin(a) │
- │ 0 0 1 │ │ Sin(a) 0 Cos(a) │ │ 0 Sin(a) Cos(a) │
- └ ┘ └ ┘ └ ┘
-
- I'm not in the mood for arithmetic now... So, do the calculations
- yourself... It's a good exercice...
- To speed up the routines a bit, you can consider precalculating the
- values of the Sin(a) and Cos(a), because they will be very used in the
- routine. Other thing you can do to speed up the calculations is to use a
- pregenerated sine/cosine table... See 3D3.PAS to see an example...
- Now, let's move on to another subject...
-
- 6.3. A 3D starfield
-
- The 3d starfield is one of the more used effects in yesterdays demos... As
- the matter of fact, there are lot's of demos that include starfields nowadays,
- with some new twist... Well, let's move on... What's a 3d starfield ?
- A 3d starfield is a starfield similar to the one I showed you in a previous
- issue of 'The Mag', but instead of just moving sideways, in the 3d starfield
- you are moving into it... You see the stars passing by...
- This is so simple to do... You just have an array of points, and you move
- them (using a translation), while you can rotate it around the Z axis, in
- order to get a cooler effect... Other thing you can do is to change the
- color of the point... The point can be darker if it is further away from the
- viewer and it gets brighter as it comes nearer.
- Other 3d starfields move around inside the starfield, but that's simple to
- do... It's just movement...
- This is so easy to make, that I'm gonna just splat the code here... Look
- at it and it will be clear...
-
- Program Starfield;
-
- Uses Mode13h,Crt;
-
- Const Points=200;
- Speed=15;
- RotateSpeed=1;
-
- Type Point3d=Record
- X,Y,Z:Real;
- End;
-
- Var Stars:Array[1..Points] of Point3d;
- A:Integer;
- X,Y:Integer;
- Color:Byte;
- D:Char;
-
- Procedure Conv3d(P:Point3d;Var Xt,Yt:Integer);
- Begin
- Xt:=160+Trunc((P.X*256)/P.Z);
- Yt:=100+Trunc((P.Y*256)/P.Z);
- End;
-
- Procedure RotateZ(Deg:Integer);
- Var Angle:Real;
- XTemp:Real;
- S,C:Real;
- Begin
- Angle:=0.0175*Deg;
- S:=Sin(Angle);
- C:=Cos(Angle);
- For A:=1 To Points Do
- Begin
- XTemp:=Stars[A].X;
- Stars[A].X:=XTemp*C-Stars[A].Y*S;
- Stars[A].Y:=Stars[A].Y*C+XTemp*S;
- End;
- End;
-
- Begin
- { Setup graphics }
- InitGraph;
- InitVirt;
- Cls(0,VGA);
- Cls(0,VP[1]);
- { Setup grayscale }
- For A:=0 To 15 Do SetColor(A,A*4,A*4,A*4);
- { Setup stars }
- For A:=1 To Points Do
- Begin
- Stars[A].X:=Random(640)-320.0;
- Stars[A].Y:=Random(400)-200.0;
- Stars[A].Z:=Random(800);
- End;
- { Main cicle }
- Repeat
- { Move stars }
- For A:=1 To Points Do Stars[A].Z:=Stars[A].Z-Speed;
- { Rotate stars }
- RotateZ(RotateSpeed);
- { Check if any stars are very near of the viewer...
- If they are, reset them... }
- For A:=1 To Points Do If Stars[A].Z<=100 Then
- Stars[A].Z:=800;
- { Clear virtual screen }
- Cls(0,VP[1]);
- { Draw stars in virtual screen }
- For A:=1 To Points Do
- Begin
- Conv3d(Stars[A],X,Y);
- { Determine color }
- Color:=15-(Trunc((16*Stars[A].Z)) Div 1000);
- { The following procedure is equal to the
- normal putpixel, except that this check if
- the point is in the boundaries of the
- screen }
- PutClippedPixel(X,Y,Color,VP[1]);
- End;
- { Copy virtual screen to VGA screen }
- CopyPage(VP[1],VGA);
- Until Keypressed;
- { Shutdown }
- CloseVirt;
- Closegraph;
- End.
-
- This is easy to understand... All the concepts were given back in this
- article... Instead of drawing lines, you just put points... This could be
- speeded up by using assembler... It is so easy to code a starfield in ASM !
- But let's move on to another cool thing...
-
- 6.4. VectorBalls
-
- What are vector balls ?
- Well, I could explain what are vector balls, but it's easier to show.
- Execute program VECTOR1.PAS and you'll see...
- It's a cool effect... It's name comes from the fact that they are balls,
- and that they use vectors... What is a vector ? A vector is a set of
- coordinates (in this case, three) that define a point in space or a
- direction... This is not a 'by the book' explanation, but it will have to
- do.
- Vectorballs have (x,y,z) coordinates that represent they're position in
- space. All you have to do is convert it's 3d coordinates to 2d coordinates
- and draw the ball... We use 2 diferent colors for the balls just to achieve
- an cooler effect...
- The only catch of vectorballs is that you must sort the vectorballs before
- drawing, so that the balls that are further away are draw first, so that
- those that are in front overwrite. If you don't do this, the effect wouldn't
- look like 3d at all... Try removing the call to the Sort procedure in the
- DrawBalls procedure... We used QuickSort, because it's the faster method,
- and speed is important in this effect.
- This is a fairly easy to do program, and it is all comented, so you
- shouldn't have dificulty in understanding it...
- If you wanted to do objects with more than two colors, it is better to
- use a base sprite and add the number of the color. If you don't understand
- what I'm talking about, check out the VECTOR2.PAS program... Check the
- DrawSprite routine in that one. That program also uses a procedure called
- LoadVector, that reads for disk a file containing the data for the color
- and base coordinates of the vectorballs. That data is generated by the
- VECTGEN.PAS program.
- You can also add movement, by using the Move procedure... Movement in
- the procedure is cool...
- This is the end of the first article on 3d in 'The Mag'... I don't know
- when I'll do the next part, but it will be about poligons and sorting...
- And maybe a little bit of lighting... But try to figure this out for
- yourself... :)
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 7. Sprites Part III - Lots of fun stuff
-
- Hello again... Let's get going with sprites in this article... This is
- gonna be a quick one, because I want to wrap this issue of 'The Mag' quickly,
- 'cos this is more than 120Kb already...
-
- 7.1. Transparency
-
- Have you noticed that when you put down a sprite, someparts of it obscures
- what it was there before ?
- Well, to solve that, we use transparency... I already talked about
- transparency in the Fonts article I did last issue... I will just go over
- the algorithm... The ideia is this: if the color of the pixel to place is 0,
- then don't put down that pixel, in the other case, put it down. Simple and
- easy... You aren't limited to color 0... It can be any color... 0 just became
- the standart... Procedure talking:
-
- Procedure PutImage_T(X,Y:Word;Var Img:Pointer;Where:Word);
- Var Dx,Dy:Word;
- A,B:Word;
- Segm,Offs:Word;
- Begin
- Segm:=Seg(Img^);
- Offs:=Ofs(Img^);
- Move(Mem[Segm:Offs],Dx,2);
- Move(Mem[Segm:Offs+2],Dy,2);
- Offs:=Offs+4;
- For A:=Y To Y+Dy-1 Do
- Begin
- For B:=X To X+Dx-1 Do
- Begin
- If Mem[Segm:Offs]<>0 Then
- PutPixel(B,A,Mem[Segm:Offs],Where);
- Inc(Offs);
- End;
- End;
- End;
-
- Procedure PutImage_CT(X,Y:Integer;Var Img:Pointer;Where:Word);
- Var Dx,Dy:Word;
- A,B:Word;
- Segm,Offs:Word;
- Begin
- Segm:=Seg(Img^);
- Offs:=Ofs(Img^);
- Move(Mem[Segm:Offs],Dx,2);
- Move(Mem[Segm:Offs+2],Dy,2);
- Offs:=Offs+4;
- A:=Y;
- While (A<=Y+DY-1) And (A<MaxY) Do
- Begin
- B:=X;
- While (B<=X+DX-1) And (B<MaxX) Do
- Begin
- If (X>=MinX) And (Y>=MinY) And (Mem[Segm:Offs]<>0) Then
- PutPixel(B,A,Mem[Segm:Offs],Where);
- Inc(Offs);
- Inc(B);
- End;
- Inc(A);
- End;
- End;
-
- There are two procedures, as you may already founded out... PutImage_T is
- for putting images using transparency and no-clipping. PutImage_CT is
- identically, but it performs clipping.
- The disadvantages of using transparency is that the procedures become
- slower. In order of speed:
-
- PutImage - No clipping / No transparency
- PutImage_T - No clipping / Transparency
- PutImage_C - Clipping / No transparency
- PutImage_CT - Clipping / Transparency
-
- You should choose carefully the type of PutImage you use, for the sake of
- speed... For example, to put down tiles, use PutImage, because you don't
- need transparency... Just use your head, before using the procedures.
- Just a note... We shouldn't call this transparency, but masking...
- Transparency is another thing, I will cover that in a future issue...
-
- 7.2. Moving over a background
-
- In almost every game sprites are required... Usually they move above a
- background of some sort (take for example the mouse pointer... That can
- be thought as a sprite). Wouldn't it be great that the sprite could be moved
- without destroying the background ? Of course it would... In some computers
- (like the AMIGA), there is a chip that does this for us... In the PC, we'll
- have to do it ourselfs... This is easy...
- All you have to do is to grab a piece of image of the same size as the
- sprite, in the same location you will put the sprite down. Then, you put the
- sprite down... Every time you erase the sprite, instead of blacking the zone
- were the sprite was, you put the image you previously grabbed, so that
- everything remains the same. Then, you start all over again... Let's check
- some code:
-
- Program SpritesOverBackground;
-
- Uses Crt,Sprites,Mode13h;
-
- Type Sprite=Record
- Img:Pointer;
- Back:Pointer;
- X,Y:Integer;
- End;
-
- Var F:File;
- Ship:Sprite;
- C:Char;
-
- Begin
- { Load sprite image }
- Assign(F,'Ship.Img');
- Reset(F,1);
- LoadImage(F,Ship.Img);
- Close(F);
- { Init graphics and set palette }
- InitGraph;
- InitVirt;
- SetColor(1,63,0,0);
- SetColor(2,63,40,0);
- SetColor(3,63,63,0);
- { Load a background }
- LoadPCX('Planet.Pcx',VP[1]);
- SetPalette(PCXPal);
- { Init ship }
- Ship.X:=0;
- Ship.Y:=100;
- { Move the ship }
- Repeat
- { Get a 27x10 square of the place were the ship is }
- GetImage(Ship.X,Ship.Y,Ship.X+27,Ship.Y+10,
- Ship.Back,VP[1]);
- { Put sprite of ship, using masking }
- PutImage_T(Ship.X,Ship.Y,Ship.Img,VP[1]);
- { Copy virtual screen to VGA screen }
- CopyPage(VP[1],VGA);
- { Restore background }
- PutImage(Ship.X,Ship.Y,Ship.Back,VP[1]);
- KillImage(Ship.Back);
- { Move ship }
- Ship.X:=Ship.X+2;
- Until (Ship.X>292) Or (KeyPressed);
- C:=ReadKey;
- KillImage(Ship.Img);
- CloseVirt;
- CloseGraph;
- End.
-
- Notice the use of virtual screens... Almost everything that involves sprites
- must use virtual screens, in order to smooth the movements... Try not using
- virtual screens in the above program and see the crappy result...
-
- 7.3. Flipping a sprite
-
- Wouldn't it be cool if the ship in the end would get back and forth ?
- Of course it would... But that would require two images, one pointing
- right and the other pointing left... Unless you use flipping... Flipping is
- a routine that inverts an image, like a mirror... This is easy to do, just
- think a bit... The first column would be the last column, the second column
- would be the 2nd last column, etc... This is horizontal flipping... For
- vertical flipping, the 1st line would become the last line, etc...
- Coding the horizontal flipping is easy:
-
- Procedure FlipHoriz(Var Img:Pointer);
- Var Dx,Dy:Word;
- S1,O1:Word;
- S2,O2:Word;
- Tmp:Pointer;
- A,B:Word;
- Begin
- { Get X and Y sizes }
- S1:=Seg(Img^);
- O1:=Ofs(Img^);
- Move(Mem[S1:O1],Dx,2);
- Move(Mem[S1:O1+2],Dy,2);
- { Create temporary sprite }
- GetMem(Tmp,Dx*Dy+4);
- S2:=Seg(Tmp^);
- O2:=Ofs(Tmp^);
- { Put the size of the sprite in the temporary sprite }
- Move(Mem[S1:O1],Mem[S2:O2],4);
- { Move the columns }
- For A:=0 To Dx-1 Do
- For B:=0 To Dy-1 Do
- Move(Mem[S1:O1+(B*Dx+A+4)],
- Mem[S2:O2+(B*Dx+(Dx-A-1)+4)],1);
- { Kill old image }
- KillImage(Img);
- { Copy new image to old one }
- Img:=Tmp;
- End;
-
- The vertical flipping is even easier:
-
- Procedure FlipVert(Var Img:Pointer);
- Var Dx,Dy:Word;
- S1,O1:Word;
- S2,O2:Word;
- Tmp:Pointer;
- A:Word;
- Begin
- { Get X and Y sizes }
- S1:=Seg(Img^);
- O1:=Ofs(Img^);
- Move(Mem[S1:O1],Dx,2);
- Move(Mem[S1:O1+2],Dy,2);
- { Create temporary sprite }
- GetMem(Tmp,Dx*Dy+4);
- S2:=Seg(Tmp^);
- O2:=Ofs(Tmp^);
- { Put the size of the sprite in the temporary sprite }
- Move(Mem[S1:O1],Mem[S2:O2],4);
- { Move the lines }
- For A:=0 To Dy-1 Do
- Move(Mem[S1:O1+(A*Dx+4)],
- Mem[S2:O2+((Dy-1-A)*Dx+4)],Dx);
- { Kill old image }
- KillImage(Img);
- { Copy new image to old one }
- Img:=Tmp;
- End;
-
- All these routines are included in the Sprites unit... You can see a test
- program that makes the ship go right and left above a background in the file
- FLYSHIP.PAS.
- Cya in the next article...
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 8. Graphics Part IX - Polygons
-
- Polygons is a comes from a greek word that means 'many angles'. Speaking in
- general terms, a polygon is a planar set of points joined by lines that is
- closed. By closed, I mean that the last point joins with the first point.
- Circles and ellipses are special polygons, because they have an infinite
- number of points.
- Arcs are just here because I want to explain them. It's just a derivation
- of ellipses.
- For now forth, when I talk about polygons, I'm talking about 4 sided
- polygons, but that concept can be extended to any number of sides. For three
- sided polygons (triangles) this gets easier, and usually more than four sides
- aren't used.
-
- 8.1. Simple polygons
-
- Four sided polygons are constructed of 4 points, each one of them having
- a (x,y) coordinate. Point 1 is joined with a line to point 2, point 2 to
- point 3, point 3 to point 4 and finally, point 4 to point 1. This is easy
- to do:
-
- Procedure Poly(X1,Y1,X2,Y2,X3,Y3,X4,Y4:Word;Color:Byte;Where:Word);
- Begin
- Line(X1,Y1,X2,Y2,Color,Where);
- Line(X2,Y2,X3,Y3,Color,Where);
- Line(X3,Y3,X4,Y4,Color,Where);
- Line(X4,Y4,X1,Y1,Color,Where);
- End;
-
- See how easy it was ? Now, let's move on to something more complex...
-
- 8.2. Filled polygons
-
- The ideia behind filled polygons is fairly simple. You just draw horizontal
- lines between the edges of the polygon. Like this:
-
- 2 Check out the horizontal lines.
- /\ The hardest thing in this way of filling polygons
- /--\3 (because they are several different ways of doing
- /---/ this) is to determine the minimum and maximum
- 1\--/ X coordinates, which we use to draw the horizontal
- \/ line.
- 4
-
- In the 8th grade (and in a previous issue) you probably learned that any
- line can be defined by the equation:
-
- (y-y0)=m(x-x0)
-
- As we want to know the X coordinate, let's solve the equation in order to
- X:
- (y-y0)
- x= ------ + x0
- m
-
- As you probably know, the m is the tangent of the line, and it is defined
- as:
- x1-x0
- m= -----
- y1-y0
-
- So, the final formula is:
-
- (y-y0)*(x1-x0)
- x= -------------- + x0
- (y1-y0)
-
- So, for any line in the polygon (in the above example, from the Y coordinate
- of point 2 to the Y coordinate of point 4), you must determine which is the
- line from which you will derive the X coordinate, to see if it is the maximum
- and minimum. To know which is the line, you simply compare see if the Y
- coordinate you are checking is between the Y coordinates of the various
- points. For example, imagine that the points in the example above had the
- following coordinates:
-
- 1: X1=5 ; Y1=25
- 2: X2=20 ; Y2=10
- 3: X3=30 ; Y3=20
- 4: X4=15 ; Y4=35
-
- And you wanted to know the maximmum and minimum X for line 17.
- Comparing the Y values, you know that line 17 will intersect the line
- between points 1 and 2, and the line between points 2 and 3. So, using the
- above coordinate, you know that the X coordinates of line 17 in those lines
- will be respectively 13 and 27. Now, you just had to draw a horizontal line
- between (13,17) to (27,17). The procedure to do the polygon follows:
-
- Procedure FPoly(X1,Y1,X2,Y2,X3,Y3,X4,Y4:Word;Color:Byte;Where:Word);
- Var MnY,MxY:Word;
- DeltaX1,DeltaX2,DeltaX3,DeltaX4:Integer;
- DeltaY1,DeltaY2,DeltaY3,DeltaY4:Integer;
- Y:Word;
- MnX,MxX:Integer;
- X:Integer;
- Begin
- { Find out the lines that are to be scanned }
- MnY:=Y1;
- MxY:=Y1;
- If MnY>Y2 Then MnY:=Y2;
- If MnY>Y3 Then MnY:=Y3;
- If MnY>Y4 Then MnY:=Y4;
- If MxY<Y2 Then MxY:=Y2;
- If MxY<Y3 Then MxY:=Y3;
- If MxY<Y4 Then MxY:=Y4;
- { Vertical clipping }
- If MnY<0 Then MnY:=0;
- If MxY>199 Then MxY:=199;
- If MnY>199 Then Exit;
- If MxY<0 Then Exit;
- { Precalculate the (x1-x0) and (y1-y0) needed later, because they
- remain the same for all the routine. They are needed to calculate
- the M. We don't calculate the M, because that would involve real
- maths that is slower than calculating in an integer form later
- in the code }
- DeltaX1:=(X1-X4); DeltaY1:=(Y1-Y4);
- DeltaX2:=(X2-X1); DeltaY2:=(Y2-Y1);
- DeltaX3:=(X3-X2); DeltaY3:=(Y3-Y2);
- DeltaX4:=(X4-X3); DeltaY4:=(Y4-Y3);
- { Main loop }
- For Y:=MnY To MnX Do
- Begin
- { Find out the minummum and maximmum X coord }
- MnX:=319;
- MxX:=-1;
- { Check if the line intersects line (X1,Y1)->(X2,Y2) }
- If (Y>=Y1) Or (Y>=Y2) Then
- If (Y<=Y1) Or (Y<=Y2) Then
- { Insure that the points don't have the same Y coord }
- If Not(Y1=Y2) Then
- Begin
- { Point of intersection }
- X:=(Y-Y1)*DeltaX2 Div DeltaY2 + X1;
- If X<MnX Then MnX:=X;
- If X>MxX Then MxX:=X;
- End;
- { Check if the line intersects line (X2,Y2)->(X3,Y3) }
- If (Y>=Y2) Or (Y>=Y3) Then
- If (Y<=Y2) Or (Y<=Y3) Then
- { Insure that the points don't have the same Y coord }
- If Not(Y2=Y3) Then
- Begin
- { Point of intersection }
- X:=(Y-Y2)*DeltaX3 Div DeltaY3 + X2;
- If X<MnX Then MnX:=X;
- If X>MxX Then MxX:=X;
- End;
- { Check if the line intersects line (X3,Y3)->(X4,Y4) }
- If (Y>=Y3) Or (Y>=Y4) Then
- If (Y<=Y3) Or (Y<=Y4) Then
- { Insure that the points don't have the same Y coord }
- If Not(Y3=Y4) Then
- Begin
- { Point of intersection }
- X:=(Y-Y3)*DeltaX4 Div DeltaY4 + X3;
- If X<MnX Then MnX:=X;
- If X>MxX Then MxX:=X;
- End;
- { Check if the line intersects line (X4,Y4)->(X1,Y1) }
- If (Y>=Y4) Or (Y>=Y1) Then
- If (Y<=Y4) Or (Y<=Y1) Then
- { Insure that the points don't have the same Y coord }
- If Not(Y4=Y1) Then
- Begin
- { Point of intersection }
- X:=(Y-Y4)*DeltaX1 Div DeltaY1 + X4;
- If X<MnX Then MnX:=X;
- If X>MxX Then MxX:=X;
- End;
- { Horizontal range checking }
- If MnX<0 Then MnX:=0;
- If MxX>319 Then MxX:=319;
- { Draw the line }
- If MnX<MxX Then Line(MnX,Y,MxX,Y,Color,Where);
- End;
- End;
-
- This looks very complicated, but it isn't... It's just confusing... This
- could be speeded up by using fixed point maths (I'll explain that in the
- next issue).
-
- 8.3. Ellipses and Arcs
-
- Ellipses are a variation of circles... This is not a correct statement.
- Circles are a variation of ellipses is more correct.
- Ellipses are curved regions of space that have an horizontal and a vertical
- diameter. Circles are when these two dimension are identical. The only
- difference between the Ellipse and Circle procedures is that the X and Y
- factors are multiplied by different factors in the Ellipse procedure.
- Check the code out:
-
- Procedure Ellipse(X,Y,RH,RV:Integer;Col:Byte;Where:Word);
- Var Px,Py:Integer;
- Deg:Word;
- Begin
- For Deg:=0 to TableElements Do
- Begin
- Px:=Trunc(RH*Sines^[Deg]+X);
- Py:=Trunc(RV*Cosines^[Deg]+Y);
- PutPixel(Px,Py,Col,Where);
- End;
- End;
-
- It's this easy... Don't forget to initialize the sine/cosine tables.
- Arcs are a variation of this. Here you specify a start and finish angle.
-
- Procedure Arc(X,Y,RH,RV:Integer;SAngle,EAngle:Integer;
- Col:Byte;Where:Word);
- Var Px,Py:Integer;
- Deg:Word;
- Begin
- { Convert the angles to table positions }
- SAngle:=Trunc(TableElements/360 * SAngle);
- EAngle:=Trunc(TableElements/360 * EAngle);
- { Draw the arc }
- For Deg:=SAngle to EAngle Do
- Begin
- Px:=Trunc(RH*Sines^[Deg]+X);
- Py:=Trunc(RV*Cosines^[Deg]+Y);
- PutPixel(Px,Py,Col,Where);
- End;
- End;
-
- Again, this is easy... Now, let's move on to:
-
- 8.4. Filled Ellipses
-
- This is very easy to do... Just follow the theory behind the polygons:
- find the minimmum and maximum X value for each line... This is easy in
- the case of ellipses. You just mirror one side of it... Code:
-
- Procedure FEllipse(X,Y,RH,RV:Integer;Col:Byte;Where:Word);
- Var Px1,Px2,Py:Integer;
- Delta:Integer;
- Deg:Word;
- Begin
- For Deg:=0 to (TableElements Div 2) Do
- Begin
- Delta:=Trunc(RH*Sines^[Deg]);
- Px1:=Delta+X;
- Px2:=X-Delta;
- Py:=Trunc(RV*Cosines^[Deg]+Y);
- Line(Px1,Py,Px2,Py,Col,Where);
- End;
- End;
-
- Well, and this is the end of the polygons' article. See you the next issue,
- where I'll teach you how to optimize and transform this and all other stuff
- I gave in previous issues in assembly code. Cya...
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 9. Hints and Tips
-
- * - Begginners tip
- ** - Medium tip
- *** - Advanced tip
-
- - Not tottally random numbers (**)
-
- Did you knew that Pascal's Random function doesn't give out random
- values ?
- As the matter of fact, there's no way a computer can give out random
- numbers. That would only be possible if the computer thinked by itself,
- and as we all know, he can't... So, how are "random" numbers formed ?
- Well... They are made by using a formula (a very complex formula,
- sometimes, a simpler one other times) which uses a base number, known as
- seed to generate the numbers.
- So, a neat trick to create the same sequence of random numbers everytime
- you run the program is to use the same seed. To do that in Pascal, you
- use the system variable RandSeed... For example, if you put this in
- your program:
- RandSeed:=1000;
-
- the seed for your numbers will be 1000... So, the sequence will be same
- everytime you run the program. Check out the article about sorting and
- see that we've used this to create the same sequence of numbers for all
- sorting procedures. This can be very usefull for games... Especially
- games were are various things that are made randomly...
- For example, if you want to create an entire galaxy... Well, a galaxy in
- Pascal could only have 65536 positions, each of these positions only
- ocupying one byte, signifying something... Using the trick of the pseudo-
- -random numbers, that number could be the seed for a section of the
- galaxy, increasing even further the size ! You could do this recursively,
- until you have the size you want !!!! Cool, isn't it ?...
-
- - Typecasting (*)
-
- Pascal has an annoying thing... I think C has the same problem. It goes
- like this... If you have a statement like this:
-
- A:=X*200;
-
- If you define X as an integer, the result of this operation will be an
- integer, even if you define A as a Longint. So, what's the problem ?
- The problem is if X is equal to (for example) 200. The result in A (in
- case A is a Longint) should be 40000, but instead, you will get the
- value -25536... Because the compiler stores the result in A as an
- integer, and not as a Longint. As the maximmum number allowed for an
- integer is 32768, the results go wrong when a calculation with an
- integer goes beyond this... A way to go around this is to do the
- following:
- A:=LongInt(X)*200;
-
- This tells the compiler to do the calculations as it would do if X was
- a LongInt. You can do typecasting (this is the name for this) with any
- kind of variable. For example, you can do:
-
- A:=Real(X)*200;
-
- Altough this would return an error, because A is a LongInt, and this
- would return a Real value.
-
-
- - With clause (*)
-
- Don't you, sometimes, get fed up of typing:
- thisvar.thisfield.thatfield.anotherfield:=10; ?
- That's why the With clause was invented... The With clause enables you
- to replace:
- Var1.Field1.Field11:=1;
- Var1.Field1.Field12:=2;
- Var1.Field2.Field21:=3;
- Var1.Field2.Field22:=4;
- by:
- With Var1 Do
- Begin
- Field1.Field11:=1;
- Field1.Field12:=2;
- Field2.Field21:=3;
- Field2.Field22:=4;
- End;
-
- Simple, isn't it ?
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-
-
-
- 10. Points of View
-
- Today's topic: three-dimensional graphics. Nowadays, a great controversy is
- installed. In one side, the purists, who are sick and tired of seeing 3d games
- and demos over and over again... On the other side, the 3d addicts, that love
- and can not live without 3d.
- I'm of a mild opinion... I like 3d games and I can enjoy a 3d sequence in a
- demo... What I can't stand is ALL games in 3d and ALL sequences in a demo
- in 3d.
- So, what's up with 3d ?
- The ideia is like this... Humans normally see the world in 3d. So, it's
- natural that a game in 3d is more realistic... People get into the game more
- easily. It's more natural...
- To keep this short, the bottom line is: Don't exagerate !! Don't do DOOM
- clones over and over again... Do something more original... Like "Dark
- Forces", by LucasArts... And in demos, don't do 3d demos... Do demos with 3d,
- instead. Again, try something original... Don't keep a torus rotating two
- hours, just because it is texture-mapped and phong-shaded, with ten millions
- sides !!! Try to put a nice phong-shaded torus, with a 21bit plasma
- background, with a nice music pumping, and keep it there for 10 seconds
- only... Then move on to shadebobs or something completely different !!!
- So, what's in the next issue of 'The Mag' ?
- Well, we'll continue our series on Text Adventures (this time, monsters and
- special rooms). I'll continue the 3d tutorial (poligons and poligon sorting,
- and maybe light-sourcing), the sprites tutorial (rotation and anything I can
- think off) and the graphics tutorial (I'll delve into assembler). I'll do an
- article on optimization and cross-fading. I think they'll be another article
- by Scorpio (this is, if I can convince him to do one).
- Well, this was quite a large issue... More than 150 Kb in size !! That's
- big !!! That represents more than 150000 characters! 'The Mag' is growing... :)
- I don't know if you noticed, but it was more or less than a year ago that
- I made the first issue... So 'The Mag' has now one year of existence...
- It would be nice if someone writes an article on how 'The Mag' has evolved
- in the course of this year... (HINT, HINT)... :)
- Well, let's move on to:
-
-
- -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x
-
-
- 11. The adventures of Spellcaster, the rebel programmer of year 2018.
-
- Episode 10 - Artificial Inteligence ?
-
- - His heart stopped... - I softly said to Kristie. - ...but his brain is
- still alive.
- Kristie tried hard to hold off the tears.
- - So what ?! - she shouted, angry.
- - I can do something... - I said, looking at the Karl's pale face.
- - What ? His heart is dead ! We can't take him to an hospital... - a bright
- tear rolled down her face.
- - I can save part of him... But it would cost him dearly...
- - What are you talking about ?!
- - He would have to loose his humanity... With the equipment we stole, I think
- I can transfer the contents of his mind to one of the new HexaMind
- computers. He would be as he ever was... But caged inside a machine.
- - I... I... don't know what to say... - she said, bowing her head.
- - Say yes... It's the only way to save him...
- - Are you sure you can do that ? - she asked, looking me directly in the
- eyes.
- - No... - I said, with a sad look on my face.
- Kristie sitted down on the floor... After a couple of minutes, she rised,
- approached Karl and looked down at his cold face. Then, she looked at the
- encefalogram and she hold Karl's hand.
- - Do it, Spell...
- An hour later, Karl's brain was hooked into the HexaMind computer, that was
- ready to read data. The HexaMind computers were an analog computer, of a new
- technologie, similar in design to our brain.
- I pressed a switch and suddently, Karl's body was shaking because of the
- huge amount of electricity it was being forced to hold.
- Minutes later, the operation was finished. The encefalogram showed zero
- activity. Or Karl was dead, or he was in the computer.
- - Karl ?... Can you ear me ? - Kristie said, approaching the large computer.
- - Kristie ?... - a synthesized voice asked. - Where am I ? I can't see you ?
- - I'll hook up a camera... - I said, reaching for a slot, plugging in the
- DeltaMatrox chip that controlled a video camera attached to it.
- - I can see again... - the voice said again, seeming lost.
- After half an hour, the explanations were made. Karl seemed calm and happy
- to be alive. Kristie was happy too... I was not. I had the burden in my soul.
- The burden of the guilt of playing God...
- I went outside, looking at the red sky above. It was near sunset. Just a
- couple of thoughts went throught my mind.
- - Is it right to destroy his humanity ? Can I play God like this ?
-
- See you in the next issue
- Diogo "SpellCaster" Andrade